Card
A surface container for grouping related content — a header (prepend slot · title · subtitle ·
append slot), a body, and optional action rows. It is the most composed styled component, yet the
standalone markup is just nested .ori-card__* elements, so it works without Vue.
The examples are organised by layer: the class reference is the standalone
@oriui/css layer, and the Framework API is the @oriui/vue component. Every
example is live — flip its code between HTML (the standalone classes, also your htmx / Astro / Svelte /
plain-HTML usage), Vue; HTML is the default.
Classes
A card is a block class plus single-class token utilities — one class repoints one token, no base class needed. The Vue props in Framework API map 1:1 to these.
| Class | Type | Description |
|---|---|---|
ori-card | Block | Required base class. Defaults bake in fill variant, surface color, and lg radius — a bare ori-card is valid. |
ori-variant_* | Style | fill · tonal · outline · text · plain |
ori-color_* | Color | primary · secondary · success · warn · danger · info · surface · background |
ori-size-radius_* | Radius | zero · xs · sm · md · lg · xl · rounded |
ori-card_fluid | Layout | full-width card |
ori-card_row | Layout | horizontal layout — header and body side by side |
ori-card__header · ori-card__header-prepend · ori-card__headline · ori-card__title · ori-card__subtitle · ori-card__header-append · ori-card__body · ori-card__actions | Part | structural BEM elements — header row · leading area · headline wrapper · title · subtitle · trailing area · body · action row |
ori-card__actions_reverse | Layout | reverses the flex direction of an action row |
aria-disabled · aria-busy | State | real attributes, not classes (disabled / loading props) |
Anatomy
The card's slot structure from top to bottom. Each named slot falls back to a corresponding prop, so simple use stays one-liner and complex layouts plug in via slots.
ori-card
└── [slot: actions-prepend] ← prepended action row (order flipped by reversePrependedActions)
└── ori-card__header
└── ori-card__header-prepend ← [slot: header-prepend] / prependAvatar / prependIcon
└── ori-card__headline
└── ori-card__title ← [slot: title] / title prop
└── ori-card__subtitle ← [slot: subtitle] / subtitle prop
└── ori-card__header-append ← [slot: header-append] / appendAvatar / appendIcon
└── ori-card__body ← [slot: body] / text prop
└── [slot: actions-append] ← appended action row (order flipped by reverseAppendedActions)
Variants
Five visual styles, all driven by the ori-variant_* utilities. The default is fill on surface.
<div class="ori-card ori-size-radius_lg ori-variant_fill ori-color_surface">
<div class="ori-card__header">
<div class="ori-card__headline"><div class="ori-card__title">Fill</div></div>
</div>
<div class="ori-card__body">Default fill on surface.</div>
</div>
<!-- swap the variant: ori-variant_fill → ori-variant_tonal / ori-variant_outline / ori-variant_text / ori-variant_plain -->
Colors
Every semantic role composes with every variant. surface is the default — neutral elevation.
<div class="ori-card ori-size-radius_lg ori-variant_tonal ori-color_danger">
<div class="ori-card__header">
<div class="ori-card__headline"><div class="ori-card__title">danger</div></div>
</div>
</div>
<!-- swap the color: ori-color_danger → ori-color_primary / ori-color_secondary / ori-color_success / ori-color_warn / ori-color_info / ori-color_surface / ori-color_background -->
Variant × color compose freely — e.g. a filled danger card or an outlined info card:
<div class="ori-card ori-size-radius_lg ori-variant_fill ori-color_danger">
<div class="ori-card__header">
<div class="ori-card__headline"><div class="ori-card__title">Delete zone</div></div>
</div>
<div class="ori-card__body">This action is irreversible.</div>
</div>
Radius
From zero (sharp) to rounded (pill-like). The default is lg.
<div class="ori-card ori-size-radius_zero ori-variant_fill ori-color_surface">
<div class="ori-card__header">
<div class="ori-card__headline"><div class="ori-card__title">zero</div></div>
</div>
<div class="ori-card__body">No corner radius.</div>
</div>
With header media
prependIcon / prependAvatar populate the leading slot; appendIcon / appendAvatar populate
the trailing slot. Pass an SVG path to prependIcon or a URL to prependAvatar.
<div class="ori-card ori-size-radius_lg ori-variant_fill ori-color_surface">
<div class="ori-card__header">
<div class="ori-card__header-prepend">
<i class="ori-icon ori-icon_sm" aria-hidden="true">
<svg viewBox="0 0 24 24"><path d="M12 2C6.48 2 2 6.48 …" /></svg>
</i>
</div>
<div class="ori-card__headline">
<div class="ori-card__title">With icon</div>
<div class="ori-card__subtitle">Leading icon via prependIcon</div>
</div>
<div class="ori-card__header-append"></div>
</div>
</div>
Row layout
row lays the header and body out horizontally — handy for compact list items or media objects.
<div class="ori-card ori-card_row ori-size-radius_lg ori-variant_fill ori-color_surface">
<div class="ori-card__header">
<div class="ori-card__header-prepend">
<i class="ori-icon ori-icon_sm" aria-hidden="true">
<svg viewBox="0 0 24 24"><path d="M22,13H13V22H11V13H2V11H11V2H13V11H22V13Z" /></svg>
</i>
</div>
<div class="ori-card__headline">
<div class="ori-card__title">Row layout</div>
<div class="ori-card__subtitle">Header and body side by side</div>
</div>
</div>
</div>
Block (fluid)
fluid stretches the card to the full width of its container.
<div class="ori-card ori-card_fluid ori-size-radius_lg ori-variant_fill ori-color_surface">
<div class="ori-card__header">
<div class="ori-card__headline"><div class="ori-card__title">Full width</div></div>
</div>
<div class="ori-card__body">This card fills its container.</div>
</div>
States
disabled and loading are expressed as real attributes — aria-disabled and aria-busy — not
classes, matching the rest of the library.
<div class="ori-card ori-size-radius_lg ori-variant_fill ori-color_surface" aria-disabled="true">
<div class="ori-card__header">
<div class="ori-card__headline"><div class="ori-card__title">Disabled</div></div>
</div>
<div class="ori-card__body">aria-disabled — dimmed, non-interactive.</div>
</div>
<div class="ori-card ori-size-radius_lg ori-variant_fill ori-color_surface" aria-busy="true">
<div class="ori-card__header">
<div class="ori-card__headline"><div class="ori-card__title">Loading</div></div>
</div>
<div class="ori-card__body">aria-busy while content is fetched.</div>
</div>
Common patterns
A profile card and a notification card with an action row — everyday compositions using slots.
<div class="ori-card ori-size-radius_lg ori-variant_fill ori-color_surface">
<div class="ori-card__header">
<div class="ori-card__header-prepend">
<i class="ori-icon ori-icon_sm" aria-hidden="true">
<svg viewBox="0 0 24 24"><path d="M12 12c2.21 0 4-1.79 4-4 …" /></svg>
</i>
</div>
<div class="ori-card__headline">
<div class="ori-card__title">Jane Smith</div>
<div class="ori-card__subtitle">Product designer</div>
</div>
</div>
<div class="ori-card__body">Leading product design at oriUI — focused on accessible, expressive systems.</div>
</div>
Notification card with an actions-append row. Use :reverse-appended-actions="true" to flip
the button order (e.g. destructive action on the right).
<!-- Notification card with action row -->
<div class="ori-card ori-size-radius_lg ori-variant_outline ori-color_warn">
<div class="ori-card__header">
<div class="ori-card__headline">
<div class="ori-card__title">Delete account?</div>
</div>
</div>
<div class="ori-card__body">This will permanently remove your data. This action cannot be undone.</div>
<div class="ori-card__actions">
<button class="ori-button ori-variant_text">Cancel</button>
<button class="ori-button ori-variant_fill ori-color_danger">Delete</button>
</div>
</div>
<!-- Reversed order: add ori-card__actions_reverse to flip flex direction -->
<div class="ori-card ori-size-radius_lg ori-variant_fill ori-color_surface">
<div class="ori-card__header">
<div class="ori-card__headline">
<div class="ori-card__title">Save changes?</div>
</div>
</div>
<div class="ori-card__body">You have unsaved edits. Would you like to save before leaving?</div>
<div class="ori-card__actions ori-card__actions_reverse">
<button class="ori-button ori-variant_text">Discard</button>
<button class="ori-button ori-variant_tonal ori-color_primary">Save</button>
</div>
</div>
Accessibility
The accessibility contract holds across every layer — the standalone classes and the Vue component render the same attributes.
disabledmaps toaria-disabled="true"+pointer-events: none— the card is dimmed but remains in the DOM and in the tab order (suitable for progressive-disclosure UIs).loadingmaps toaria-busy="true"— assistive technology announces that the region is updating. Combine with a visible spinner inside thebodyslot when appropriate.- Decorative icons (
prependIcon,appendIcon) are rendered via<OriIcon>which setsaria-hidden="true"automatically. - Card does not manage focus itself. If the card triggers a navigation or dialog, add the
appropriate role (
role="button",tabindex="0") and keyboard handler to the root element via attribute fall-through. - The body uses the
on-surfacetoken paired with the card's surface color, keeping text contrast WCAG AA on every skin.
Framework API
The props, events, and slots of the Vue component. The standalone CSS layer has no component API — its surface is the classes above. (Svelte bindings are planned.)
Props
| Prop | Type | Default | Description |
|---|---|---|---|
appendAvatar | string | — | URL for an avatar in the trailing header slot. |
appendIcon | string | — | SVG path for an icon in the trailing header slot. |
color | ThemeColor | 'surface' | Semantic role: primary · secondary · success · warn · danger · info · surface · background. |
disabled | boolean | — | Sets aria-disabled="true" and pointer-events: none. |
fluid | boolean | — | Stretches the card to full container width (ori-card_fluid). |
image | string | — | Reserved for a hero image URL (not yet rendered by the template). |
loading | boolean | — | Sets aria-busy="true" and pointer-events: none. |
prependAvatar | string | — | URL for an avatar in the leading header slot. |
prependIcon | string | — | SVG path for an icon in the leading header slot. |
radius | RadiusSize | 'lg' | Corner radius: zero · xs · sm · md · lg · xl · rounded. |
reverseAppendedActions | boolean | — | Reverses the flex direction of the actions-append row. |
reversePrependedActions | boolean | — | Reverses the flex direction of the actions-prepend row. |
row | boolean | — | Horizontal layout — header and body side by side (ori-card_row). |
subtitle | string | — | Subtitle below the title; falls back to the subtitle slot. |
text | string | — | Body text; falls back to the body slot. |
title | string | — | Primary heading; falls back to the title slot. |
variant | Variant | 'fill' | Visual style: fill · tonal · outline · text · plain. |
Events & attributes
OriCard declares no custom events and does not set inheritAttrs: false, so native attributes
(id, class, style, data-*, aria-*, event listeners, etc.) fall through to the root <div>.
Slots
| Slot | Falls back to | Description |
|---|---|---|
default | — | Replaces the entire built-in card structure. You own the full interior markup. |
actions-prepend | (nothing — slot must be used) | Action row rendered before the header. reversePrependedActions flips its order. |
header-prepend | prependAvatar / prependIcon | Leading area of the header row (avatar, icon, or custom media). |
title | title prop | Primary heading inside the headline. |
subtitle | subtitle prop | Secondary line below the title. |
header-append | appendAvatar / appendIcon | Trailing area of the header row (avatar, icon, or custom control). |
body | text prop | Main content area below the header. |
actions-append | (nothing — slot must be used) | Action row rendered after the body. reverseAppendedActions flips its order. |