Usage
Card groups content and actions about a single subject. Import Card from @udixio/ui-react or @udixio/ui-angular. The component owns the container surface only: padding, typography, and inner layout come from your own content, passed as React children or Angular projected content.
Variants
variant selects the container treatment: outlined (default), elevated, and filled.
Outlined
Elevated
Filled
import { Card } from '@udixio/ui-react';
export default function CardVariantsReact() {
return (
<div className="flex w-full flex-wrap gap-4">
<Card className="flex h-40 min-w-48 flex-1 items-center justify-center">
<p>Outlined</p>
</Card>
<Card
variant="elevated"
className="flex h-40 min-w-48 flex-1 items-center justify-center"
>
<p>Elevated</p>
</Card>
<Card
variant="filled"
className="flex h-40 min-w-48 flex-1 items-center justify-center"
>
<p>Filled</p>
</Card>
</div>
);
}Outlined
Elevated
Filled
import { ChangeDetectionStrategy, Component } from '@angular/core';
import { Card } from '@udixio/ui-angular';
@Component({
selector: 'docs-card-variants-angular',
standalone: true,
imports: [Card],
changeDetection: ChangeDetectionStrategy.OnPush,
template: `
<div class="flex w-full flex-wrap gap-4">
<udx-card
class="flex h-40 min-w-48 flex-1 items-center justify-center"
>
<p>Outlined</p>
</udx-card>
<udx-card
variant="elevated"
class="flex h-40 min-w-48 flex-1 items-center justify-center"
>
<p>Elevated</p>
</udx-card>
<udx-card
variant="filled"
class="flex h-40 min-w-48 flex-1 items-center justify-center"
>
<p>Filled</p>
</udx-card>
</div>
`,
})
export class CardVariantsAngular {}Outlined
Elevated
Filled
<script lang="ts">
import { Card } from '@udixio/ui-svelte';
</script>
<div class="flex w-full flex-wrap gap-4">
<Card class="flex h-40 min-w-48 flex-1 items-center justify-center">
<p>Outlined</p>
</Card>
<Card variant="elevated" class="flex h-40 min-w-48 flex-1 items-center justify-center">
<p>Elevated</p>
</Card>
<Card variant="filled" class="flex h-40 min-w-48 flex-1 items-center justify-center">
<p>Filled</p>
</Card>
</div>Actionable cards
A card becomes actionable in two ways, and both apply the same visual treatment: the shared state layer, a pointer cursor, and a visible focus outline.
hrefrenders a native<a>. Use it for navigation tiles. Angular also exposestargetandrel.interactivewithouthrefrendersrole="button"withtabindex="0"and activates on Enter and Space, matching native button keyboard behavior. Bind the action with ReactonClickor Angular(click); keyboard activation goes through the same click event.
Navigate to the Card page
Run an action
No action yet
import { useState } from 'react';
import { Card } from '@udixio/ui-react';
export default function CardActionsReact() {
const [message, setMessage] = useState('No action yet');
return (
<div className="flex w-full flex-col items-center gap-3">
<div className="flex w-full flex-wrap gap-4">
<Card
href="/components/card/overview"
variant="elevated"
className="flex h-40 min-w-48 flex-1 items-center justify-center"
>
<p>Navigate to the Card page</p>
</Card>
<Card
interactive
onClick={() => setMessage('Card activated')}
variant="filled"
className="flex h-40 min-w-48 flex-1 items-center justify-center"
>
<p>Run an action</p>
</Card>
</div>
<p aria-live="polite" className="text-body-medium">
{message}
</p>
</div>
);
}Navigate to the Card page
Run an action
No action yet
import { ChangeDetectionStrategy, Component, signal } from '@angular/core';
import { Card } from '@udixio/ui-angular';
@Component({
selector: 'docs-card-actions-angular',
standalone: true,
imports: [Card],
changeDetection: ChangeDetectionStrategy.OnPush,
template: `
<div class="flex w-full flex-col items-center gap-3">
<div class="flex w-full flex-wrap gap-4">
<udx-card
href="/components/card/overview"
variant="elevated"
class="flex h-40 min-w-48 flex-1 items-center justify-center"
>
<p>Navigate to the Card page</p>
</udx-card>
<udx-card
interactive
variant="filled"
class="flex h-40 min-w-48 flex-1 items-center justify-center"
(click)="message.set('Card activated')"
>
<p>Run an action</p>
</udx-card>
</div>
<p aria-live="polite" class="text-body-medium">{{ message() }}</p>
</div>
`,
})
export class CardActionsAngular {
protected readonly message = signal('No action yet');
}Navigate to the Card page
Run an action
No action yet
<script lang="ts">
import { Card } from '@udixio/ui-svelte';
let message = $state('No action yet');
</script>
<div class="flex w-full flex-col items-center gap-3">
<div class="flex w-full flex-wrap gap-4">
<Card
href="/components/card/overview"
variant="elevated"
class="flex h-40 min-w-48 flex-1 items-center justify-center"
>
<p>Navigate to the Card page</p>
</Card>
<Card
interactive
variant="filled"
class="flex h-40 min-w-48 flex-1 items-center justify-center"
onclick={() => (message = 'Card activated')}
>
<p>Run an action</p>
</Card>
</div>
<p aria-live="polite" class="text-body-medium">{message}</p>
</div>An actionable card is a single action target. Do not nest links or buttons inside one: the nested control would be unreachable by keyboard in a predictable order and the card’s accessible name would absorb its text. Put inner controls in a non-interactive card instead, as shown below.
Composition
Card has no built-in header, media, or actions slots. Compose those regions with your own markup and other Udixio components.
Project Aurora
Last updated 2 days ago
import { Button, Card } from '@udixio/ui-react';
export default function CardCompositionReact() {
return (
<Card variant="filled" className="w-full max-w-96">
<img
className="h-40 w-full object-cover"
src="https://picsum.photos/640/240"
alt=""
/>
<div className="space-y-2 p-4">
<p className="text-title-medium">Project Aurora</p>
<p className="text-body-medium text-on-surface-variant">
Last updated 2 days ago
</p>
<div className="flex gap-2 pt-2">
<Button label="Open" size="small" variant="filled" />
<Button
label="Share"
size="small"
variant="text"
edgeAligned={false}
/>
</div>
</div>
</Card>
);
}Project Aurora
Last updated 2 days ago
import { ChangeDetectionStrategy, Component } from '@angular/core';
import { Button, Card } from '@udixio/ui-angular';
@Component({
selector: 'docs-card-composition-angular',
standalone: true,
imports: [Button, Card],
changeDetection: ChangeDetectionStrategy.OnPush,
template: `
<udx-card variant="filled" class="w-full max-w-96">
<img
class="h-40 w-full object-cover"
src="https://picsum.photos/640/240"
alt=""
/>
<div class="space-y-2 p-4">
<p class="text-title-medium">Project Aurora</p>
<p class="text-body-medium text-on-surface-variant">
Last updated 2 days ago
</p>
<div class="flex gap-2 pt-2">
<udx-button label="Open" size="small" variant="filled" />
<udx-button
label="Share"
size="small"
variant="text"
[edgeAligned]="false"
/>
</div>
</div>
</udx-card>
`,
})
export class CardCompositionAngular {}Project Aurora
Last updated 2 days ago
<script lang="ts">
import { Button, Card } from '@udixio/ui-svelte';
</script>
<Card variant="filled" class="w-full max-w-96">
<img class="h-40 w-full object-cover" src="https://picsum.photos/640/240" alt="" />
<div class="space-y-2 p-4">
<p class="text-title-medium">Project Aurora</p>
<p class="text-body-medium text-on-surface-variant">Last updated 2 days ago</p>
<div class="flex gap-2 pt-2">
<Button label="Open" size="small" variant="filled" />
<Button label="Share" size="small" variant="text" edgeAligned={false} />
</div>
</div>
</Card>Accessibility
A non-interactive card is a plain container: it carries no role, no tab stop, and no state layer, so assistive technology reads its content directly. An actionable card takes its accessible name from its content, so keep meaningful text inside it. Link cards rely on native anchor semantics; interactive cards expose button semantics with keyboard activation and a two-pixel focus outline.
Migration to the next major
| Previous API | Stable API |
|---|---|
interactive as a visual affordance | interactive also provides button semantics |
| Custom overlay link inside the card | href on the card itself |
interactive previously added the state layer and pointer cursor without any role, tab stop, or keyboard activation, which made “clickable” cards unreachable by keyboard. It now renders role="button" with tabindex="0". If you relied on the purely visual behavior and supplied your own semantics with a nested control, move that destination to href on the card, or drop interactive on the container that wraps your own interactive element.