# Card

Use card when several pieces of related information should stay together as one scanable unit. Good cards have a clear hierarchy, concise actions, and only enough chrome to separate one record from another. Prefer focused layouts over generic content dumping.

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

## Basic content grouping
```ts
import { Component } from '@angular/core';
import { ButtonComponent, CardComponent, BadgeComponent } from 'ui';

@Component({
  selector: 'app-card-basic-demo',
  standalone: true,
  imports: [ButtonComponent, CardComponent, BadgeComponent],
  template: `
    <div
      style="display:grid;grid-template-columns:repeat(auto-fit,minmax(16rem,1fr));gap:1rem;width:100%;"
    >
      <ui-card appearance="filled" [interactive]="true" ariaLabel="Quarterly planning card">
        <div
          uiCardPreview
          style="min-height:7rem;background:linear-gradient(135deg,#dcecff 0%,#bdd7f7 100%);"
        ></div>

        <div uiCardHeader style="display:grid;gap:0.25rem;">
          <strong style="font-size:1rem;line-height:1.35;">Quarterly planning</strong>
          <span style="font-size:0.8125rem;color:var(--color-neutral-foreground3-rest);">
            6 milestones aligned across product and engineering
          </span>
        </div>

        <div uiCardBody style="display:grid;gap:0.75rem;">
          <p
            style="margin:0;font-size:0.875rem;line-height:1.5;color:var(--color-neutral-foreground2-rest);"
          >
            Keep the primary summary short and push secondary details into compact metadata or
            follow-up views.
          </p>
        </div>

        <div uiCardFooter style="display:flex;gap:0.5rem;flex-wrap:wrap;">
          <ui-button variant="primary" appearance="filled">Open</ui-button>
          <ui-button variant="secondary" appearance="outline">Share</ui-button>
        </div>
      </ui-card>

      <ui-card appearance="filled" borderStyle="dashed" ariaLabel="Support inbox card">
        <div uiCardHeader style="display:grid;gap:0.25rem;">
          <strong style="font-size:1rem;line-height:1.35;">Support inbox</strong>
          <span style="font-size:0.8125rem;color:var(--color-neutral-foreground3-rest);">
            14 unresolved conversations
          </span>
        </div>

        <div uiCardBody style="display:grid;gap:0.75rem;">
          <p
            style="margin:0;font-size:0.875rem;line-height:1.5;color:var(--color-neutral-foreground2-rest);"
          >
            Outline cards work well when the surrounding page already has elevation and you only
            need clear grouping.
          </p>
          <div style="display:flex;flex-wrap:wrap;gap:0.5rem;">
            <ui-badge text="4 urgent" shape="circular" variant="danger" appearance="filled" />
            <ui-badge
              text="7 waiting on customer"
              shape="circular"
              variant="info"
              appearance="filled"
            />
          </div>
        </div>
      </ui-card>
    </div>
  `,
})
export class CardBasicDemoComponent {}
```

## Appearance, border, and orientation
```ts
import { Component } from '@angular/core';
import { CardComponent } from 'ui';

@Component({
  selector: 'app-card-surfaces-demo',
  standalone: true,
  imports: [CardComponent],
  template: `
    <div style="display:grid;gap:1rem;width:100%;">
      <div style="display:grid;grid-template-columns:repeat(auto-fit,minmax(15rem,1fr));gap:1rem;">
        <ui-card appearance="filled" ariaLabel="Filled card">
          <div uiCardHeader style="display:grid;gap:0.25rem;">
            <strong style="font-size:0.9375rem;">Filled</strong>
            <span style="font-size:0.75rem;color:var(--color-neutral-foreground3-rest);"
              >Dense, default emphasis</span
            >
          </div>
        </ui-card>

        <ui-card appearance="filled-alternative" ariaLabel="Filled alternative card">
          <div uiCardHeader style="display:grid;gap:0.25rem;">
            <strong style="font-size:0.9375rem;">Filled alternative</strong>
            <span style="font-size:0.75rem;color:var(--color-neutral-foreground3-rest);"
              >Slightly richer surface for nested modules</span
            >
          </div>
        </ui-card>

        <ui-card appearance="subtle" ariaLabel="Subtle card">
          <div uiCardHeader style="display:grid;gap:0.25rem;">
            <strong style="font-size:0.9375rem;">Subtle</strong>
            <span style="font-size:0.75rem;color:var(--color-neutral-foreground3-rest);"
              >Low-chrome shell for already structured layouts</span
            >
          </div>
        </ui-card>
      </div>

      <div style="display:grid;grid-template-columns:repeat(auto-fit,minmax(15rem,1fr));gap:1rem;">
        <ui-card appearance="outline" ariaLabel="Outline card">
          <div uiCardHeader style="display:grid;gap:0.25rem;">
            <strong style="font-size:0.9375rem;">Outline</strong>
            <span style="font-size:0.75rem;color:var(--color-neutral-foreground3-rest);"
              >Good when the surrounding page already supplies elevation and grouping</span
            >
          </div>
        </ui-card>

        <ui-card
          appearance="filled-alternative"
          borderStyle="none"
          ariaLabel="Soft card without border"
        >
          <div uiCardHeader style="display:grid;gap:0.25rem;">
            <strong style="font-size:0.9375rem;">Soft shell</strong>
            <span style="font-size:0.75rem;color:var(--color-neutral-foreground3-rest);"
              >Remove the border when cards should feel more like quiet content sections</span
            >
          </div>
        </ui-card>

        <ui-card appearance="outline" borderStyle="dashed" ariaLabel="Dashed border card">
          <div uiCardHeader style="display:grid;gap:0.25rem;">
            <strong style="font-size:0.9375rem;">Dashed border</strong>
            <span style="font-size:0.75rem;color:var(--color-neutral-foreground3-rest);"
              >Use border style when the shell should feel more editorial, draft-like, or
              utility-oriented</span
            >
          </div>
        </ui-card>
      </div>

      <ui-card appearance="outline" orientation="horizontal" ariaLabel="Horizontal media card">
        <div
          uiCardPreview
          style="width:10rem;min-height:100%;background:linear-gradient(135deg,#f8e9d7 0%,#efc9a1 100%);"
        ></div>

        <div uiCardHeader style="display:grid;gap:0.25rem;">
          <strong style="font-size:1rem;line-height:1.35;">Horizontal layout</strong>
          <span style="font-size:0.8125rem;color:var(--color-neutral-foreground3-rest);">
            Better when a preview should sit beside short metadata instead of above it
          </span>
        </div>

        <div uiCardBody>
          <p
            style="margin:0;font-size:0.875rem;line-height:1.5;color:var(--color-neutral-foreground2-rest);"
          >
            Horizontal cards are useful for compact media rows, attachment previews, and list-style
            summaries.
          </p>
        </div>
      </ui-card>
    </div>
  `,
})
export class CardSurfacesDemoComponent {}
```

## Focus modes for embedded actions
```ts
import { Component } from '@angular/core';
import { ButtonComponent, CardComponent } from 'ui';

@Component({
  selector: 'app-card-focus-mode-demo',
  standalone: true,
  imports: [ButtonComponent, CardComponent],
  template: `
    <div style="display:grid;gap:1rem;width:100%;">
      <ui-card appearance="outline" borderStyle="dashed" ariaLabel="Focus mode helper">
        <div
          uiCardBody
          style="font-size:0.8125rem;line-height:1.5;color:var(--color-neutral-foreground2-rest);"
        >
          Focus the cards below with Tab. On no-tab and tab-exit, press Enter on the card root to
          move into the inner actions.
        </div>
      </ui-card>

      <div style="display:grid;grid-template-columns:repeat(auto-fit,minmax(16rem,1fr));gap:1rem;">
        <ui-card [interactive]="true" focusMode="off" ariaLabel="Card with focus mode off">
          <div uiCardHeader style="display:grid;gap:0.25rem;">
            <strong style="font-size:0.9375rem;">focusMode="off"</strong>
            <span style="font-size:0.75rem;color:var(--color-neutral-foreground3-rest);"
              >Card acts as a surface; inner buttons use normal tab order</span
            >
          </div>
          <div uiCardFooter style="display:flex;gap:0.5rem;flex-wrap:wrap;">
            <ui-button variant="primary" appearance="outline">Open</ui-button>
            <ui-button variant="secondary" appearance="outline">Archive</ui-button>
          </div>
        </ui-card>

        <ui-card
          [interactive]="true"
          focusMode="tab-only"
          ariaLabel="Card with focus mode tab only"
        >
          <div uiCardHeader style="display:grid;gap:0.25rem;">
            <strong style="font-size:0.9375rem;">focusMode="tab-only"</strong>
            <span style="font-size:0.75rem;color:var(--color-neutral-foreground3-rest);"
              >Tab from the card root moves directly into the first embedded control</span
            >
          </div>
          <div uiCardFooter style="display:flex;gap:0.5rem;flex-wrap:wrap;">
            <ui-button variant="primary" appearance="outline">Inspect</ui-button>
            <ui-button variant="secondary" appearance="outline">Share</ui-button>
          </div>
        </ui-card>

        <ui-card [interactive]="true" focusMode="no-tab" ariaLabel="Card with focus mode no tab">
          <div uiCardHeader style="display:grid;gap:0.25rem;">
            <strong style="font-size:0.9375rem;">focusMode="no-tab"</strong>
            <span style="font-size:0.75rem;color:var(--color-neutral-foreground3-rest);"
              >Tab loops inside the card actions until Escape returns focus to the root</span
            >
          </div>
          <div uiCardFooter style="display:flex;gap:0.5rem;flex-wrap:wrap;">
            <ui-button variant="primary" appearance="outline">Review</ui-button>
            <ui-button variant="secondary" appearance="outline">Assign</ui-button>
          </div>
        </ui-card>

        <ui-card
          [interactive]="true"
          focusMode="tab-exit"
          ariaLabel="Card with focus mode tab exit"
        >
          <div uiCardHeader style="display:grid;gap:0.25rem;">
            <strong style="font-size:0.9375rem;">focusMode="tab-exit"</strong>
            <span style="font-size:0.75rem;color:var(--color-neutral-foreground3-rest);"
              >Tab can leave the card after the last action; Shift+Tab returns to the root</span
            >
          </div>
          <div uiCardFooter style="display:flex;gap:0.5rem;flex-wrap:wrap;">
            <ui-button variant="primary" appearance="outline">Resolve</ui-button>
            <ui-button variant="secondary" appearance="outline">Mute</ui-button>
          </div>
        </ui-card>
      </div>
    </div>
  `,
})
export class CardFocusModeDemoComponent {}
```

## Selection and checkbox patterns
```ts
import { Component, signal } from '@angular/core';
import { FormsModule } from '@angular/forms';
import { ButtonComponent, CardComponent, CardOnSelectionChangeEvent, CheckboxComponent } from 'ui';

@Component({
  selector: 'app-card-selection-demo',
  standalone: true,
  imports: [FormsModule, ButtonComponent, CardComponent, CheckboxComponent],
  template: `
    <div style="display:grid;gap:1rem;width:100%;">
      <div style="display:grid;grid-template-columns:repeat(auto-fit,minmax(16rem,1fr));gap:1rem;">
        <ui-card
          [selectable]="true"
          [interactive]="true"
          [selected]="surfaceSelected()"
          borderStyle="dashed"
          ariaLabel="Selectable card surface"
          (selectionChange)="onSurfaceSelectionChange($event)"
        >
          <div uiCardHeader style="display:grid;gap:0.25rem;">
            <strong style="font-size:0.9375rem;">Click-anywhere selection</strong>
            <span style="font-size:0.75rem;color:var(--color-neutral-foreground3-rest);"
              >Useful for galleries or bulk management where the whole tile represents one
              choice</span
            >
          </div>
        </ui-card>

        <ui-card
          [selectable]="true"
          [selected]="checkboxSelected()"
          ariaLabel="Selectable card with projected checkbox"
          (selectionChange)="onCheckboxSelectionChange($event)"
        >
          <ui-checkbox
            uiCardCheckbox
            [label]="''"
            [ariaLabel]="'Select card'"
            [ngModel]="checkboxSelected()"
            [ngModelOptions]="{ standalone: true }"
            (click)="$event.stopPropagation()"
            (change)="onProjectedCheckboxChange($event)"
          />

          <div uiCardHeader style="display:grid;gap:0.25rem;">
            <strong style="font-size:0.9375rem;">Projected checkbox</strong>
            <span style="font-size:0.75rem;color:var(--color-neutral-foreground3-rest);"
              >Better when users expect an explicit selection affordance instead of a surface
              click</span
            >
          </div>
        </ui-card>
      </div>

      <ui-card appearance="outline" borderStyle="dashed" ariaLabel="Selection status panel">
        <div
          uiCardBody
          style="display:flex;flex-wrap:wrap;gap:0.75rem;align-items:center;justify-content:space-between;"
        >
          <div style="display:flex;flex-wrap:wrap;gap:1rem 1.5rem;align-items:center;">
            <span style="font-size:0.8125rem;color:var(--color-neutral-foreground2-rest);">
              Surface card:
              <strong style="color:var(--color-neutral-foreground-rest);">{{
                surfaceSelected() ? 'selected' : 'not selected'
              }}</strong>
            </span>
            <span style="font-size:0.8125rem;color:var(--color-neutral-foreground2-rest);">
              Checkbox card:
              <strong style="color:var(--color-neutral-foreground-rest);">{{
                checkboxSelected() ? 'selected' : 'not selected'
              }}</strong>
            </span>
          </div>

          <ui-button variant="secondary" appearance="outline" (click)="reset()">Reset</ui-button>
        </div>
      </ui-card>
    </div>
  `,
})
export class CardSelectionDemoComponent {
  protected readonly surfaceSelected = signal(true);
  protected readonly checkboxSelected = signal(false);

  protected onSurfaceSelectionChange(event: CardOnSelectionChangeEvent): void {
    this.surfaceSelected.set(event.data.selected);
  }

  protected onCheckboxSelectionChange(event: CardOnSelectionChangeEvent): void {
    this.checkboxSelected.set(event.data.selected);
  }

  protected onProjectedCheckboxChange(value: boolean): void {
    this.checkboxSelected.set(!!value);
  }

  protected reset(): void {
    this.surfaceSelected.set(true);
    this.checkboxSelected.set(false);
  }
}
```

## Floating actions and projected slots
```ts
import { Component, signal } from '@angular/core';
import { ButtonComponent, CardComponent } from 'ui';

@Component({
  selector: 'app-card-floating-actions-demo',
  standalone: true,
  imports: [ButtonComponent, CardComponent],
  template: `
    <div
      style="display:grid;grid-template-columns:repeat(auto-fit,minmax(17rem,1fr));gap:1rem;width:100%;"
    >
      <ui-card [interactive]="true" ariaLabel="Card with floating quick action">
        <ui-button
          uiCardFloatingAction
          variant="secondary"
          appearance="subtle"
          icon="pin"
          [ariaLabel]="pinned() ? 'Unpin card' : 'Pin card'"
          (click)="togglePinned()"
        />

        <div
          uiCardPreview
          style="min-height:7.5rem;background:linear-gradient(135deg,#eef4ff 0%,#d3e3fb 100%);"
        ></div>

        <div uiCardHeader style="display:grid;gap:0.25rem;">
          <strong style="font-size:0.9375rem;">Floating utility action</strong>
          <span style="font-size:0.75rem;color:var(--color-neutral-foreground3-rest);">
            Keep the body clean when the action is secondary but should stay immediately visible
          </span>
        </div>

        <div uiCardBody>
          <p
            style="margin:0;font-size:0.875rem;line-height:1.5;color:var(--color-neutral-foreground2-rest);"
          >
            Pinned state:
            <strong style="color:var(--color-neutral-foreground-rest);">{{
              pinned() ? 'pinned' : 'not pinned'
            }}</strong>
          </p>
        </div>
      </ui-card>

      <ui-card ariaLabel="Card with projected preview and action rail">
        <ui-button
          uiCardFloatingAction
          variant="secondary"
          appearance="subtle"
          icon="more_horizontal"
          ariaLabel="More actions"
        />

        <div
          uiCardPreview
          style="display:grid;place-items:center;min-height:7.5rem;background:linear-gradient(135deg,#f7efe4 0%,#efd6b1 100%);font-size:0.8125rem;font-weight:600;color:var(--color-neutral-foreground2-rest);"
        >
          Preview slot
        </div>

        <div uiCardHeader style="display:grid;gap:0.25rem;">
          <strong style="font-size:0.9375rem;">Projected slots</strong>
          <span style="font-size:0.75rem;color:var(--color-neutral-foreground3-rest);"
            >uiCardPreview and uiCardFloatingAction adapt the same primitive to richer record
            layouts</span
          >
        </div>
      </ui-card>
    </div>
  `,
})
export class CardFloatingActionsDemoComponent {
  protected readonly pinned = signal(true);

  protected togglePinned(): void {
    this.pinned.update(value => !value);
  }
}
```

## Composition patterns
```ts
import { Component } from '@angular/core';
import { AvatarComponent, ButtonComponent, CardComponent } from 'ui';

@Component({
  selector: 'app-card-composition-demo',
  standalone: true,
  imports: [AvatarComponent, ButtonComponent, CardComponent],
  template: `
    <div
      style="display:grid;grid-template-columns:repeat(auto-fit,minmax(17rem,1fr));gap:1rem;width:100%;"
    >
      <ui-card appearance="filled" [interactive]="true" ariaLabel="Product card">
        <div
          uiCardPreview
          style="min-height:9rem;background:radial-gradient(circle at 25% 20%,rgba(255,255,255,0.6),transparent 28%),linear-gradient(145deg,#edf5ff 0%,#c9dbf4 100%);"
        ></div>

        <div uiCardHeader style="display:grid;gap:0.25rem;">
          <strong style="font-size:1rem;line-height:1.35;">Noise-canceling headphones</strong>
          <span style="font-size:0.8125rem;color:var(--color-neutral-foreground3-rest);"
            >$249 · In stock</span
          >
        </div>

        <div uiCardBody style="display:grid;gap:0.75rem;">
          <p
            style="margin:0;font-size:0.875rem;line-height:1.5;color:var(--color-neutral-foreground2-rest);"
          >
            Keep commerce cards focused on product value, stock state, and one primary purchase
            action.
          </p>
        </div>

        <div uiCardFooter style="display:flex;gap:0.5rem;flex-wrap:wrap;">
          <ui-button variant="primary" appearance="filled">Add to cart</ui-button>
          <ui-button variant="secondary" appearance="outline">Compare</ui-button>
        </div>
      </ui-card>

      <ui-card appearance="filled-alternative" [interactive]="true" ariaLabel="Team member card">
        <div uiCardHeader style="display:flex;align-items:flex-start;gap:0.75rem;">
          <ui-avatar name="Adriana Nowak" variant="primary" appearance="filled" />
          <div style="display:grid;gap:0.25rem;min-width:0;flex:1;">
            <strong style="font-size:1rem;line-height:1.35;">Adriana Nowak</strong>
            <span style="font-size:0.8125rem;color:var(--color-neutral-foreground3-rest);"
              >Senior Product Designer</span
            >
          </div>
        </div>

        <div uiCardBody style="display:grid;gap:0.75rem;">
          <p
            style="margin:0;font-size:0.875rem;line-height:1.5;color:var(--color-neutral-foreground2-rest);"
          >
            Profile cards work best when they answer who this person is, what they own, and the next
            likely action.
          </p>
          <div style="display:flex;flex-wrap:wrap;gap:0.5rem;">
            <span
              style="padding:0.25rem 0.5rem;border-radius:999px;background:var(--color-neutral-background-rest);font-size:0.75rem;color:var(--color-neutral-foreground2-rest);"
            >
              Design systems
            </span>
            <span
              style="padding:0.25rem 0.5rem;border-radius:999px;background:var(--color-neutral-background-rest);font-size:0.75rem;color:var(--color-neutral-foreground2-rest);"
            >
              Research ops
            </span>
          </div>
        </div>

        <div uiCardFooter style="display:flex;gap:0.5rem;flex-wrap:wrap;">
          <ui-button variant="primary" appearance="filled">Message</ui-button>
          <ui-button variant="secondary" appearance="outline">View profile</ui-button>
        </div>
      </ui-card>
    </div>
  `,
})
export class CardCompositionDemoComponent {}
```

## Accessibility

### Accessible name
`CardComponent` should expose a meaningful name when it is interactive, selectable, or focusable. Use `ariaLabel`, `ariaLabelledBy`, and `ariaDescribedBy` to tie the surface to visible title and supporting text instead of leaving screen readers with a generic group.

### Keyboard
Keyboard behavior depends on `focusMode`, `interactive`, and selection settings.

| Key | Action |
| --- | --- |
| Tab | Moves to the card when it is focusable, or through inner controls according to `focusMode`. |
| Enter / Space | Activates selection or `cardClick` when focus is on the card root. |
| Enter | In `no-tab` and `tab-exit`, enters inner action navigation when focus is on the card root. |
| Escape | Leaves active focus mode and returns focus to the card root. |
| Tab / Shift+Tab | Cycles or exits inner controls depending on `focusMode`. |

### Semantics
Card uses `role="group"` by default and can expose `aria-selected` when selection is enabled. This is appropriate for grouped content surfaces, but card is not a replacement for semantic list markup, links, or buttons.

| Aspect | Behavior |
| --- | --- |
| Default role | `group` |
| Selection state | `aria-selected` when selection is enabled |
| Disabled state | `aria-disabled="true"` when disabled |
| Focus target | the card root when focusable; inner controls depend on `focusMode` |
| Name source | `ariaLabel`, `ariaLabelledBy`, or visible projected content |
