# File

Use `ui-file` when users need to attach documents, images, or bundles without building a custom upload shell. It supports compact inline selection, larger drop zones, selected-file lists, multiple uploads, and shared field inputs like label, help text, size, variant, readonly, disabled, and validation messaging.

## Import
```ts
import { FileComponent } from 'ui';
```

## Basic upload patterns
```ts
import { Component } from '@angular/core';
import { FileComponent } from 'ui';

@Component({
  selector: 'app-file-basic-demo',
  standalone: true,
  imports: [FileComponent],
  template: `
    <div style="display:flex;flex-wrap:wrap;gap:1rem;align-items:flex-start;width:100%">
      <div
        style="flex:1 1 18rem;min-width:16rem;display:flex;flex-direction:column;gap:1rem;padding:1rem;border:1px solid var(--color-neutral-stroke-rest);border-radius:1rem;background:var(--color-neutral-background-rest)"
      >
        <div style="display:flex;flex-direction:column;gap:0.25rem">
          <div style="font-size:0.9375rem;font-weight:600">Project brief</div>
          <div style="font-size:0.8125rem;color:var(--color-neutral-foreground2-rest)">
            A roomy drop zone works well when upload is the primary action in the section.
          </div>
        </div>

        <ui-file
          label="Upload file"
          helpText="PDF, DOC, or DOCX up to 10 MB."
          accept=".pdf,.doc,.docx"
        />
      </div>

      <div
        style="flex:1 1 18rem;min-width:16rem;display:flex;flex-direction:column;gap:1rem;padding:1rem;border:1px solid var(--color-neutral-stroke-rest);border-radius:1rem;background:var(--color-neutral-background-rest)"
      >
        <div style="display:flex;flex-direction:column;gap:0.25rem">
          <div style="font-size:0.9375rem;font-weight:600">Inline asset picker</div>
          <div style="font-size:0.8125rem;color:var(--color-neutral-foreground2-rest)">
            Inline mode fits compact forms, rows, and dialogs where upload is a secondary step.
          </div>
        </div>

        <ui-file
          mode="inline"
          label="Logo file"
          placeholder="Select a replacement logo"
          helpText="SVG or PNG."
          accept=".svg,.png"
        />
      </div>
    </div>
  `,
})
export class FileBasicDemoComponent {}
```

## Layout, size, and label position
```ts
import { Component } from '@angular/core';
import { FileComponent } from 'ui';

@Component({
  selector: 'app-file-layout-demo',
  standalone: true,
  imports: [FileComponent],
  template: `
    <div style="display:flex;flex-direction:column;gap:1rem;width:100%;max-width:46rem">
      <div
        style="display:flex;flex-wrap:wrap;gap:1rem;align-items:flex-start;padding:1rem;border:1px solid var(--color-neutral-stroke-rest);border-radius:1rem;background:var(--color-neutral-background-rest)"
      >
        <div style="flex:1 1 15rem;min-width:14rem">
          <ui-file
            label="Compact receipt"
            labelPosition="above"
            mode="inline"
            size="small"
            inputVariant="filled-lighter"
            placeholder="Attach receipt"
          />
        </div>

        <div style="flex:1 1 15rem;min-width:14rem">
          <ui-file
            label="Contract scan"
            labelPosition="before"
            mode="inline"
            size="medium"
            inputVariant="filled"
            placeholder="Choose file"
          />
        </div>
      </div>

      <div
        style="display:flex;flex-direction:column;gap:0.875rem;padding:1rem;border:1px solid var(--color-neutral-stroke-rest);border-radius:1rem;background:var(--color-neutral-background-rest)"
      >
        <div style="font-size:0.9375rem;font-weight:600">Large upload step</div>
        <ui-file
          label="Campaign assets"
          size="large"
          inputVariant="filled"
          helpText="Use the larger surface when upload is the main task in the view."
          uploadText="Click to upload campaign assets or drag them here"
          uploadHint="PNG, JPG, MP4 files up to 25 MB"
          accept="image/*,video/*"
        />
      </div>
    </div>
  `,
})
export class FileLayoutDemoComponent {}
```

## Accept filters and limits
```ts
import { Component } from '@angular/core';
import { FileComponent } from 'ui';

@Component({
  selector: 'app-file-filters-limits-demo',
  standalone: true,
  imports: [FileComponent],
  template: `
    <div style="display:flex;flex-wrap:wrap;gap:1rem;align-items:flex-start;width:100%">
      <div style="flex:1 1 16rem;min-width:15rem">
        <ui-file
          label="Brand images"
          accept="image/*"
          [maxSize]="5 * 1024 * 1024"
          uploadText="Drop images here"
          uploadHint="PNG or JPG up to 5 MB each"
        />
      </div>

      <div style="flex:1 1 16rem;min-width:15rem">
        <ui-file
          label="Signed agreement"
          mode="inline"
          accept=".pdf"
          placeholder="Select signed PDF"
          helpText="One PDF only."
        />
      </div>

      <div style="flex:1 1 16rem;min-width:15rem">
        <ui-file
          label="Evidence bundle"
          [multiple]="true"
          [maxFiles]="3"
          [maxSize]="10 * 1024 * 1024"
          accept=".pdf,.png,.jpg"
          uploadText="Upload up to 3 supporting files"
          uploadHint="PDF, PNG, JPG up to 10 MB each"
        />
      </div>
    </div>
  `,
})
export class FileFiltersLimitsDemoComponent {}
```

## Multiple files and built-in list
```ts
import { Component } from '@angular/core';
import { FormsModule } from '@angular/forms';
import { ButtonComponent, FileComponent } from 'ui';

@Component({
  selector: 'app-file-multiple-demo',
  standalone: true,
  imports: [ButtonComponent, FileComponent, FormsModule],
  template: `
    <div style="display:flex;flex-wrap:wrap;gap:1rem;align-items:flex-start;width:100%">
      <div style="flex:1 1 22rem;min-width:18rem;max-width:34rem">
        <ui-file
          label="Upload supporting files"
          [multiple]="true"
          [maxFiles]="5"
          accept=".pdf,.png,.jpg,.zip"
          uploadText="Drop files here or browse from your device"
          uploadHint="Up to 5 files. ZIP, PDF, PNG, JPG."
          [(ngModel)]="files"
          [ngModelOptions]="{ standalone: true }"
        />
      </div>

      <div
        style="flex:0 0 17rem;display:flex;flex-direction:column;gap:0.75rem;min-width:15rem;padding:0.875rem 1rem;border:1px dashed var(--color-neutral-stroke-rest);border-radius:1rem;background:var(--color-neutral-background2-rest)"
      >
        <p
          style="margin:0;font-size:0.75rem;font-weight:600;letter-spacing:0.06em;text-transform:uppercase;color:var(--color-neutral-foreground2-rest)"
        >
          Selection
        </p>

        <div
          style="display:flex;flex-direction:column;gap:0.5rem;font-size:0.875rem;line-height:1.4"
        >
          <div style="display:flex;justify-content:space-between;gap:1rem">
            <span style="color:var(--color-neutral-foreground2-rest)">Files</span>
            <strong style="font-weight:600;color:var(--color-neutral-foreground-rest)">{{
              files.length
            }}</strong>
          </div>
          <div style="display:flex;justify-content:space-between;gap:1rem">
            <span style="color:var(--color-neutral-foreground2-rest)">Total size</span>
            <strong style="font-weight:600;color:var(--color-neutral-foreground-rest)">{{
              formatBytes(totalBytes)
            }}</strong>
          </div>
          <div style="display:flex;justify-content:space-between;gap:1rem">
            <span style="color:var(--color-neutral-foreground2-rest)">Last file</span>
            <strong style="font-weight:600;color:var(--color-neutral-foreground-rest)">{{
              files.at(-1)?.name ?? 'None'
            }}</strong>
          </div>
        </div>

        <div
          style="display:flex;gap:0.75rem;flex-wrap:wrap;align-items:center;padding-top:0.75rem;border-top:1px solid color-mix(in srgb,var(--color-neutral-stroke-rest) 65%,transparent)"
        >
          <ui-button type="button" variant="secondary" appearance="outline" (click)="seedFiles()">
            Load example files
          </ui-button>
          <ui-button type="button" appearance="subtle" (click)="files = []">Clear</ui-button>
        </div>
      </div>
    </div>
  `,
})
export class FileMultipleDemoComponent {
  protected files: File[] = [
    new File(['roadmap'], 'roadmap.pdf', { type: 'application/pdf' }),
    new File(['image'], 'hero-banner.png', { type: 'image/png' }),
  ];

  protected get totalBytes(): number {
    return this.files.reduce((sum, file) => sum + file.size, 0);
  }

  protected seedFiles(): void {
    this.files = [
      new File(['brief'], 'brief-v3.pdf', { type: 'application/pdf' }),
      new File(['archive'], 'source-files.zip', { type: 'application/zip' }),
      new File(['preview'], 'mobile-preview.jpg', { type: 'image/jpeg' }),
    ];
  }

  protected formatBytes(bytes: number): string {
    if (bytes < 1024) {
      return `${bytes} B`;
    }
    if (bytes < 1024 * 1024) {
      return `${(bytes / 1024).toFixed(1)} KB`;
    }
    return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
  }
}
```

## Validation, readonly, and disabled
```ts
import { Component } from '@angular/core';
import { FormsModule } from '@angular/forms';
import { FormControl, ReactiveFormsModule, Validators } from '@angular/forms';
import { ButtonComponent, FileComponent } from 'ui';

@Component({
  selector: 'app-file-states-validation-demo',
  standalone: true,
  imports: [ButtonComponent, FileComponent, FormsModule, ReactiveFormsModule],
  template: `
    <div style="display:flex;flex-direction:column;gap:1rem;width:100%;max-width:48rem">
      <div style="display:flex;flex-wrap:wrap;gap:1rem;align-items:flex-start">
        <div style="flex:1 1 20rem;min-width:16rem">
          <ui-file
            label="Signed approval"
            [formControl]="requiredFileControl"
            helpText="Required before publishing."
            accept=".pdf"
          />
        </div>

        <div style="flex:1 1 20rem;min-width:16rem;display:flex;flex-direction:column;gap:1rem">
          <ui-file
            mode="inline"
            label="Readonly archive"
            [readonly]="true"
            [(ngModel)]="readonlyFile"
            [ngModelOptions]="{ standalone: true }"
          />

          <ui-file
            label="Disabled upload"
            [disabled]="true"
            uploadText="Upload is disabled for archived records"
            uploadHint="Contact an administrator to change files."
          />
        </div>
      </div>

      <div
        style="display:flex;gap:0.75rem;flex-wrap:wrap;align-items:center;padding:0.75rem 0.875rem;border:1px dashed var(--color-neutral-stroke-rest);border-radius:0.875rem;background:var(--color-neutral-background-rest)"
      >
        <ui-button type="button" variant="primary" (click)="validateRequiredFile()">
          Validate required upload
        </ui-button>
        <span style="font-size:0.8125rem;color:var(--color-neutral-foreground2-rest)">
          Readonly keeps the current file visible. Disabled removes interaction entirely.
        </span>
      </div>
    </div>
  `,
})
export class FileStatesValidationDemoComponent {
  protected readonly requiredFileControl = new FormControl<File | null>(null, {
    validators: [Validators.required],
  });

  protected readonly readonlyFile = new File(['copy'], 'archived-brief.pdf', {
    type: 'application/pdf',
  });

  protected validateRequiredFile(): void {
    this.requiredFileControl.markAsTouched();
    this.requiredFileControl.updateValueAndValidity();
  }
}
```

## Reactive form integration
```ts
import { Component } from '@angular/core';
import { FormControl, FormGroup, ReactiveFormsModule } from '@angular/forms';
import { ButtonComponent, FileComponent } from 'ui';

@Component({
  selector: 'app-file-form-demo',
  standalone: true,
  imports: [ButtonComponent, FileComponent, ReactiveFormsModule],
  template: `
    <form
      [formGroup]="uploadForm"
      style="display:flex;flex-wrap:wrap;gap:1rem;align-items:flex-start;width:100%;max-width:54rem"
    >
      <div
        style="flex:1 1 22rem;min-width:18rem;display:flex;flex-direction:column;gap:1rem;padding:1rem;border:1px solid var(--color-neutral-stroke-rest);border-radius:1rem;background:var(--color-neutral-background-rest)"
      >
        <div style="display:flex;flex-direction:column;gap:0.25rem">
          <div style="font-size:0.9375rem;font-weight:600">Workspace asset update</div>
          <div style="font-size:0.8125rem;color:var(--color-neutral-foreground2-rest)">
            Pair a single cover asset with a multi-file attachment group inside one reactive form.
          </div>
        </div>

        <ui-file
          label="Cover image"
          formControlName="coverImage"
          accept="image/*"
          mode="inline"
          placeholder="Choose cover image"
          helpText="Recommended size: 1600 x 900 px."
        />

        <ui-file
          label="Reference files"
          formControlName="attachments"
          [multiple]="true"
          [maxFiles]="4"
          accept=".pdf,.fig,.zip,.png"
          uploadText="Upload files for review"
          uploadHint="Up to 4 files."
        />

        <div
          style="display:flex;gap:0.75rem;flex-wrap:wrap;align-items:center;padding:0.75rem 0.875rem;border:1px dashed var(--color-neutral-stroke-rest);border-radius:0.875rem;background:var(--color-neutral-background-rest)"
        >
          <ui-button type="button" variant="primary">Save draft</ui-button>
          <ui-button type="button" variant="secondary" appearance="outline"
            >Send for review</ui-button
          >
          <ui-button type="button" appearance="subtle" (click)="resetForm()">Reset</ui-button>
        </div>
      </div>

      <div
        style="flex:0 0 18rem;min-width:16rem;padding:0.875rem 1rem;border:1px dashed var(--color-neutral-stroke-rest);border-radius:1rem;background:var(--color-neutral-background2-rest)"
      >
        <p
          style="margin:0 0 0.5rem;font-size:0.75rem;font-weight:600;letter-spacing:0.06em;text-transform:uppercase;color:var(--color-neutral-foreground2-rest)"
        >
          Form values
        </p>
        <div
          style="display:flex;flex-direction:column;gap:0.5rem;font-size:0.875rem;line-height:1.4"
        >
          <div style="display:flex;justify-content:space-between;gap:1rem">
            <span style="color:var(--color-neutral-foreground2-rest)">Cover image</span>
            <strong style="font-weight:600;color:var(--color-neutral-foreground-rest)">{{
              uploadForm.controls.coverImage.value?.name ?? 'None'
            }}</strong>
          </div>
          <div style="display:flex;justify-content:space-between;gap:1rem">
            <span style="color:var(--color-neutral-foreground2-rest)">Attachments</span>
            <strong style="font-weight:600;color:var(--color-neutral-foreground-rest)">{{
              uploadForm.controls.attachments.value?.length ?? 0
            }}</strong>
          </div>
          <div
            style="margin-top:0.25rem;padding-top:0.5rem;font-size:0.75rem;color:var(--color-neutral-foreground2-rest);border-top:1px solid color-mix(in srgb,var(--color-neutral-stroke-rest) 65%,transparent)"
          >
            Use this pattern when the upload is only one part of a larger form flow.
          </div>
        </div>
      </div>
    </form>
  `,
})
export class FileFormDemoComponent {
  protected readonly uploadForm = new FormGroup({
    coverImage: new FormControl<File | null>(
      new File(['cover'], 'cover-image.png', { type: 'image/png' }),
    ),
    attachments: new FormControl<File[] | null>([
      new File(['brief'], 'campaign-brief.pdf', { type: 'application/pdf' }),
    ]),
  });

  protected resetForm(): void {
    this.uploadForm.reset({
      coverImage: null,
      attachments: [],
    });
  }
}
```

## Document request panel
```ts
import { Component } from '@angular/core';
import { FormsModule } from '@angular/forms';
import { ButtonComponent, FileComponent } from 'ui';

@Component({
  selector: 'app-file-request-panel-demo',
  standalone: true,
  imports: [ButtonComponent, FileComponent, FormsModule],
  template: `
    <div
      style="display:flex;flex-direction:column;gap:1rem;width:100%;max-width:42rem;padding:1rem;border:1px solid var(--color-neutral-stroke-rest);border-radius:1rem;background:var(--color-neutral-background-rest)"
    >
      <div style="display:flex;flex-direction:column;gap:0.375rem">
        <div style="font-size:1rem;font-weight:600">Client handoff package</div>
        <div
          style="font-size:0.875rem;color:var(--color-neutral-foreground2-rest);line-height:1.45"
        >
          Collect the signed deck, export archive, and any screenshots before publishing the final
          delivery package.
        </div>
      </div>

      <ui-file
        label="Primary files"
        [multiple]="true"
        [maxFiles]="5"
        accept=".pdf,.zip,.png,.jpg"
        uploadText="Upload handoff materials"
        uploadHint="Signed deck, ZIP export, screenshots."
        [(ngModel)]="handoffFiles"
        [ngModelOptions]="{ standalone: true }"
      />

      <ui-file
        label="Release note"
        mode="inline"
        accept=".txt,.md,.pdf"
        placeholder="Optional release note"
        [(ngModel)]="releaseNote"
        [ngModelOptions]="{ standalone: true }"
      />

      <div
        style="display:flex;gap:0.75rem;flex-wrap:wrap;align-items:center;padding:0.75rem 0.875rem;border:1px dashed var(--color-neutral-stroke-rest);border-radius:0.875rem;background:var(--color-neutral-background-rest)"
      >
        <ui-button type="button" variant="primary">Send package</ui-button>
        <ui-button type="button" variant="secondary" appearance="outline">Save draft</ui-button>
        <div style="font-size:0.8125rem;color:var(--color-neutral-foreground2-rest)">
          {{ handoffFiles.length }} file{{ handoffFiles.length === 1 ? '' : 's' }} ready for review
        </div>
      </div>
    </div>
  `,
})
export class FileRequestPanelDemoComponent {
  protected handoffFiles: File[] = [
    new File(['summary'], 'release-summary.pdf', { type: 'application/pdf' }),
    new File(['archive'], 'workspace-export.zip', { type: 'application/zip' }),
  ];

  protected releaseNote: File | null = null;
}
```

## Accessibility

### Label and description
Prefer a visible `label` so the upload purpose is explicit. Help and error text are linked through `aria-describedby`, and `ariaLabel` can provide the trigger name when there is no visible label.

### Trigger semantics
Both modes are driven by a native file input that stays out of the tab order while the visible trigger exposes button-like interaction. Inline mode uses a readonly text field with `role="button"`; area mode uses the drop zone surface with `role="button"`.

### Keyboard
| Key | Action |
| --- | --- |
| Tab | Moves focus to the visible file trigger and, when files are present in area mode, to remove actions. |
| Enter / Space | Opens the native file picker from the visible trigger. |
| Shift+Tab | Returns focus to the previous control in the form flow. |

### File list actions
When area mode shows selected files, each remove button gets a file-specific accessible name such as `Remove contract.pdf`. The hidden native input remains `tabindex="-1"`, so users do not encounter duplicate focus stops.
