# Accordion A disclosure list built entirely on native `
`/`` elements — zero JavaScript, zero ARIA reinvention. The browser supplies the disclosure role, `aria-expanded`, Enter/Space toggling, focus, and find-in-page expansion for free. Single-open (exclusive) mode uses the platform exclusive-accordion feature (Baseline 2024): every `
` shares one auto-generated `name`, so the browser closes siblings when one opens. `multiple` drops the shared `name` so each item opens independently. The examples are organised by **layer**: the [class reference](https://oriui.vercel.app/#classes) is the standalone **`@oriui/css`** layer, and the [Framework API](https://oriui.vercel.app/#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 The accordion is a block class plus single-class token utilities — one class repoints one token, no base class needed. The Vue props in [Framework API](https://oriui.vercel.app/#framework-api) map 1:1 to these. ::class-table --- rows: - class: ori-accordion type: Block description: The accordion container — a wrapper
that carries the colour and optional radius utilities; hairline border with clipped corners. - class: ori-color_* type: Color description: primary · secondary · success · warn · danger · info · surface - class: ori-size-radius_* type: Radius description: zero · xs · sm · md · lg · xl · rounded - class: ori-accordion__item type: Part description: A native
element, one per item. Open state is the native open attribute, styled via details[open]. - class: ori-accordion__trigger type: Part description: The native ; the default marker is suppressed and a custom chevron added; focus ring via :focus-visible. - class: ori-accordion__title type: Part description: The item title text; takes the accent colour when its item is open. - class: ori-accordion__icon type: Part description: The decorative chevron (aria-hidden); rotates when its item is open. - class: ori-accordion__panel type: Part description: The disclosure body wrapper that holds the panel content. - class: aria-disabled + tabindex type: State description: A disabled trigger carries real aria-disabled and tabindex=-1 attributes, not classes. --- :: ## Anatomy ```text div.ori-accordion (wrapper; carries the color + optional radius utilities) details.ori-accordion__item (one per item; browser owns open attribute) summary.ori-accordion__trigger span.ori-accordion__title (heading text) svg.ori-accordion__icon (decorative chevron; aria-hidden) div.ori-accordion__panel (default scoped slot body) ``` ## Basic A single-open accordion (default). Opening one item closes the previous one — enforced natively by the shared `name` attribute, no JavaScript required. ::example :::ori-accordion --- items: - value: one title: What is oriUI? - value: two title: How do I install it? - value: three title: Does it work without Vue? --- ::: #vue ```vue ``` #html ```html
What is oriUI?

oriUI is a layered Vue 3 component library.

``` :: ## Multiple open `multiple` lets any number of items be open simultaneously — each `
` opens and closes independently. ::example :::ori-accordion --- items: - value: step1 title: Step 1 — Install - value: step2 title: Step 2 — Configure - value: step3 title: Step 3 — Deploy multiple: true --- ::: #vue ```vue ``` #html ```html
Step 1 — Install

Details for Step 1 — Install.

``` :: ## Colors Every semantic role. The accent color is applied to the open item's title and the rotating chevron. ::example :::ori-accordion --- items: - value: a title: Primary accent color: primary --- ::: :::ori-accordion --- items: - value: a title: Secondary accent color: secondary --- ::: :::ori-accordion --- items: - value: a title: Success accent color: success --- ::: :::ori-accordion --- items: - value: a title: Danger accent color: danger --- ::: :::ori-accordion --- items: - value: a title: Info accent color: info --- ::: #vue ```vue ``` #html ```html
``` :: ## Radius Corner rounding via the `radius` prop. When omitted, the container uses the baked default `md` (the `.ori-accordion` block bakes `--ori-size-radius: md`, so a bare block is never square). ::example :::ori-accordion --- items: - value: a title: zero — no rounding radius: zero --- ::: :::ori-accordion --- items: - value: a title: sm — subtle rounding radius: sm --- ::: :::ori-accordion --- items: - value: a title: md — medium rounding radius: md --- ::: :::ori-accordion --- items: - value: a title: lg — large rounding radius: lg --- ::: :::ori-accordion --- items: - value: a title: xl — extra-large rounding radius: xl --- ::: #vue ```vue ``` #html ```html
``` :: ## Disabled items Set `disabled: true` on individual items in the `items` array to block interaction on those triggers. Disabled items render `aria-disabled="true"` and `tabindex="-1"` on their ``. ::example :::ori-accordion --- items: - value: billing title: Billing - value: security title: Security (restricted) disabled: true - value: notifications title: Notifications --- ::: #vue ```vue ``` #html ```html
Security (restricted)
``` :: ## Common patterns Colors, radius, and rich slot content together — a common FAQ or settings pattern. ::example :::ori-accordion --- items: - value: cost title: Is oriUI free to use? - value: framework title: Which frameworks are supported? - value: theming title: How does theming work? color: secondary radius: md --- ::: #vue ```vue ``` #html ```html
Is oriUI free to use?

Yes — MIT licensed. Use it in personal and commercial projects.

``` :: ## Accessibility The accessibility contract holds across every layer — the standalone classes and the Vue component render the same attributes and keyboard behaviour. - Built entirely on native `
`/``: the browser supplies `role="group"` on the `
` and `role="button"` with `aria-expanded` on the `` — no ARIA reinvention. - Single-open mode is enforced by the platform exclusive-accordion (shared `name` attribute, Baseline 2024), not script — the browser closes siblings automatically. - Disabled items use `aria-disabled="true"` and `tabindex="-1"` on the `` (state via real attributes, styled with attribute selectors). - The chevron SVG is `aria-hidden="true"` — it is purely decorative; the title text is the accessible name for each section. - Focus is always visible via `:focus-visible` on the `` trigger (inset outline so it shows on the row); hover is gated behind `@media (hover: hover)`. | Key | Action | | ------- | ------------------------------------------------------------------------ | | `Enter` | Toggles the focused disclosure item open or closed (native). | | `Space` | Toggles the focused disclosure item open or closed (native). | | `Tab` | Moves focus to the next focusable element inside or after the accordion. | ## Framework API The props, events, slots, and standalone CSS surface of the **Vue** component. The standalone CSS layer has no component API — its surface is the [classes](https://oriui.vercel.app/#classes) above. (Svelte bindings are planned.) ### Props | Prop | Type | Default | Description | | ---------- | ---------------------------------------------------------------------- | ----------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `color` | `ThemeColor` | `'primary'` | Accent color for the open item's title and the rotating chevron. Repoints `--ori-color` via the `ori-color_` utility class on the wrapper. | | `items` | `Array<{ value: string | number; title: string; disabled?: boolean }>` | — | **Required.** The disclosure items. `value` is used as the `:key`; `title` is the summary text; `disabled` sets `aria-disabled="true"` + `tabindex="-1"` and blocks pointer events on that trigger. | | `multiple` | `boolean` | `false` | `false` = exclusive single-open (shared `name`, browser closes siblings). `true` = each item opens/closes independently (no shared `name`). | | `radius` | `RadiusSize` | `'md'` | Optional corner radius for the accordion container. Attaches `ori-size-radius_` when set; the bare block bakes in `md` as its default. | ### Events & attributes `OriAccordion` declares **no custom emits**. Open state is owned by the browser's native `
` mechanism — there is no `v-model` and no Vue toggle. The component does not set `inheritAttrs: false`, so extra attributes (`class`, `data-*`, `id`, …) fall through to the root `div.ori-accordion`. ### Slots | Slot | Scope | Description | | --------- | ------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------- | | `title` | `{ item: { value: string | number; title: string; disabled?: boolean } }` | Scoped per-item trigger content, rendered inside each item's `.ori-accordion__title`. Receives the current item; falls back to the `item.title` string. | | `default` | `{ item: { value: string | number; title: string; disabled?: boolean } }` | Scoped per-item panel body, rendered inside each item's `.ori-accordion__panel`. Receives the current item; falls back to an empty panel if unused. | # Alert A styled, accessible notification banner. Its live-region politeness is derived from the color — urgent `danger` / `warn` render `role="alert"` (assertive), everything else `role="status"` (polite) — so a non-urgent banner isn't announced assertively (override with the `live` prop). It supports an optional icon, a title, body text, an actions row, and a dismiss button. The examples are organised by **layer**: the [class reference](https://oriui.vercel.app/#classes) is the standalone **`@oriui/css`** layer, and the [Framework API](https://oriui.vercel.app/#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 An alert is a block class plus single-class token utilities — one class repoints one token; no base class needed. The Vue props in [Framework API](https://oriui.vercel.app/#framework-api) map 1:1 to these. ::class-table --- rows: - class: ori-alert type: Block description: Required base class. The Vue component sets role=alert (danger/warn) or role=status (otherwise); standalone markup picks the role itself. - class: ori-variant_* type: Style description: fill · tonal · outline · text · plain - class: ori-color_* type: Color description: primary · secondary · success · warn · danger · info · surface · background - class: ori-size-radius_* type: Radius description: zero · xs · sm · md · lg · xl · rounded - class: ori-font-size_* type: Size description: xs · sm · md · lg · xl · xxl — scales the body text - class: ori-alert__icon · ori-alert__content · ori-alert__title · ori-alert__body · ori-alert__actions · ori-alert__close type: Part description: internal layout elements --- :: Like Button, the non-fill variants (the default `tonal` included) paint the body text with the AA-safe `--ori-color-text` tone rather than the raw role — see [Design tokens](https://oriui.vercel.app/guides/design-tokens#text-the-on-surface-foreground). ## Variants Five visual styles, all driven by the `ori-variant_*` utility. `tonal` is the default. ::example :ori-alert{color="info" text="Fill alert" variant="fill"} :ori-alert{color="info" text="Tonal alert" variant="tonal"} :ori-alert{color="info" text="Outline alert" variant="outline"} :ori-alert{color="info" text="Text alert" variant="text"} :ori-alert{color="info" text="Plain alert" variant="plain"} #vue ```vue ``` #html ```html
Tonal alert
``` :: ## Colors Every semantic role. Use `success`, `warn`, `danger`, and `info` for status messages. ::example :ori-alert{color="primary" text="Primary"} :ori-alert{color="secondary" text="Secondary"} :ori-alert{color="success" text="Success"} :ori-alert{color="warn" text="Warning"} :ori-alert{color="danger" text="Danger"} :ori-alert{color="info" text="Info"} #vue ```vue ``` #html ```html ``` :: Variant and color compose freely — e.g. an outline danger alert: ::example :ori-alert{color="danger" text="Something went wrong. Please try again." variant="outline"} :ori-alert{color="success" text="Your changes have been saved." variant="fill"} #vue ```vue ``` #html ```html ``` :: ## With title Pass `title` to add a bold heading above the body text. ::example :ori-alert{color="warn" text="Please sign in again to continue." title="Session expired"} #vue ```vue ``` #html ```html ``` :: ## With icon Pass an SVG path to `icon` to prepend a visual cue. ::example :::ori-alert --- color: success icon: M9 16.17 4.83 12l-1.42 1.41L9 19 21 7l-1.41-1.41z text: Your profile has been updated. title: Success --- ::: :::ori-alert --- color: warn icon: M1 21h22L12 2 1 21zm12-3h-2v-2h2v2zm0-4h-2v-4h2v4z text: Disk space is running low. title: Warning --- ::: :::ori-alert --- color: danger icon: M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm1 15h-2v-2h2v2zm0-4h-2V7h2v6z text: Failed to load data. title: Error --- ::: #vue ```vue ``` #html ```html
Success
Your profile has been updated.
``` :: ## Dismissible `closable` adds a close button in the top-right corner. Handle the `close` event to remove or hide the alert. The button label defaults to `"Dismiss"` and is configurable via `closeLabel`. ::example :::ori-alert --- closable: true color: info text: A new version is ready to install. title: Update available --- ::: #vue ```vue ``` #html ```html
Update available
A new version is ready to install.
``` :: ## Sizes `size` scales the body text from `xs` to `xxl`. The default is `md`. ::example :ori-alert{color="info" size="xs" text="Extra small alert"} :ori-alert{color="info" size="sm" text="Small alert"} :ori-alert{color="info" size="md" text="Medium alert (default)"} :ori-alert{color="info" size="lg" text="Large alert"} #vue ```vue ``` #html ```html
Large alert
``` :: ## Radius From `zero` (sharp corners) to `rounded` (pill). Default is `md`. ::example :ori-alert{color="info" radius="zero" text="No radius"} :ori-alert{color="info" radius="sm" text="Small radius"} :ori-alert{color="info" radius="md" text="Medium radius (default)"} :ori-alert{color="info" radius="lg" text="Large radius"} :ori-alert{color="info" radius="rounded" text="Rounded"} #vue ```vue ``` #html ```html
Rounded
``` :: ## Common patterns A form submission result and a persistent banner with actions — the everyday compositions. ::example :::ori-alert --- closable: true color: danger icon: M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm1 15h-2v-2h2v2zm0-4h-2V7h2v6z text: Your card was declined. Check your details and try again. title: Payment failed variant: outline --- ::: #vue ```vue ``` #html ```html ``` :: ## Accessibility The accessibility contract holds across every layer — the standalone classes and the Vue component render the same attributes. - The **live-region politeness is derived from the color** so non-urgent content isn't announced assertively: urgent colors (`danger` / `warn`) render `role="alert"` (`aria-live="assertive"` — interrupts the screen reader), and everything else (the `info` default, `success`, …) renders `role="status"` (`aria-live="polite"` — announced at the next pause). Override with the `live` prop (`assertive` / `polite` / `off`); `off` removes the live region entirely (for content that is part of the static page and shouldn't be announced as it appears). - Icons inside the alert are decorative (`aria-hidden="true"` on the `ori-icon`); the alert text itself is the accessible announcement. - The dismiss button has an `aria-label` (default `"Dismiss"`, configurable via `closeLabel`) so its purpose is clear without visible text. - The dismiss button has a visible `:focus-visible` ring. No other keyboard interaction is required; the alert itself is not focusable. - For a **live** announcement, insert (or reveal) the element with its content already in place — some screen readers announce only content present when the region appears. For content that is part of the page on load, prefer `live="off"` (or a non-urgent color) so it isn't announced as the page renders. | Key | Action | | ------- | ----------------------------------------------- | | `Tab` | Moves focus to the dismiss button (if present). | | `Enter` | Activates the focused dismiss button. | | `Space` | Activates the focused dismiss button. | ## Framework API The props, events, slots, and slots of the **Vue** component. The standalone CSS layer has no component API — its surface is the [classes](https://oriui.vercel.app/#classes) above. (Svelte bindings are planned.) ### Props | Prop | Type | Default | Description | | ------------ | ------------------------------------------------- | ----------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `closable` | `boolean` | `false` | Renders a dismiss button; emit `close` to remove the alert. | | `closeLabel` | `string` | `'Dismiss'` | `aria-label` on the dismiss button. | | `color` | `ThemeColor` | `'info'` | Semantic role: primary · secondary · success · warn · danger · info · surface · background. | | `icon` | `string` | — | SVG path for the leading icon. Use the `icon` slot for custom markup. | | `live` | `'assertive' | 'polite' | 'off'` | *derived* | Live-region politeness. Defaults to `assertive` (`role="alert"`) for `danger` / `warn` and `polite` (`role="status"`) otherwise; `off` removes the live region. | | `radius` | `RadiusSize` | `'md'` | Corner radius (`zero` · xs · sm · md · lg · xl · `rounded`). | | `size` | `ActionSize` | `'md'` | Body font scale (`xs`–`xxl`). | | `text` | `string` | — | Body text. Use the `default` slot for richer markup. | | `title` | `string` | — | Bold heading above the body. Use the `title` slot for richer markup. | | `variant` | `'fill' | 'tonal' | 'outline' | 'text' | 'plain'` | `'tonal'` | Visual style. | `ThemeColor`: `'primary' | 'secondary' | 'success' | 'warn' | 'danger' | 'info' | 'surface' | 'background'` `ActionSize`: `'text' | 'xs' | 'sm' | 'md' | 'lg' | 'xl' | 'xxl'` `RadiusSize`: `'zero' | 'xs' | 'sm' | 'md' | 'lg' | 'xl' | 'rounded'` ### Events & attributes | Event | Payload | Description | | ------- | ------- | ------------------------------------------------------------------------------- | | `close` | — | Emitted when the dismiss button is clicked. Use it to hide or remove the alert. | OriAlert does not set `inheritAttrs: false`, so extra attributes (`class`, `style`, `data-*`, …) fall through to the root `
` (whose `role` is `alert` / `status` / none per the `live` rule above). ### Slots | Slot | Description | | --------- | -------------------------------------------------------------------------------------------------------------------------------------- | | `default` | Body content. Replaces the `text` prop; renders inside `ori-alert__body`. | | `icon` | Custom icon markup. Replaces the `icon` prop; renders inside `ori-alert__icon`. | | `title` | Custom title markup. Replaces the `title` prop; renders inside `ori-alert__title`. | | `actions` | Action controls (buttons, links) rendered in a flex row below the body (`ori-alert__actions`). Only mounted when the slot is provided. | # Avatar A data-display component that shows an image, or initials derived from `text` when there is no image (or while it loads). Supports a title / subtitle column for list and profile UIs. The examples are organised by **layer**: the [class reference](https://oriui.vercel.app/#classes) is the standalone **`@oriui/css`** layer, and the [Framework API](https://oriui.vercel.app/#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 An avatar is a block class plus single-class token utilities — one class repoints one token; no separate base class is needed. The Vue props in [Framework API](https://oriui.vercel.app/#framework-api) mirror these — font size and the titled layout are derived from `size` and `title` / `subtitle`. There is no variant; an optional `color` tints the initials backdrop. ::class-table --- rows: - class: ori-avatar type: Block description: Required base class. - class: ori-color_* type: Color description: primary · secondary · success · warn · danger · info · surface · background — tints the initials backdrop - class: ori-avatar_* (size) type: Size description: xs · sm · md · lg · xl · xxl - class: ori-size-action-space_* type: Size description: adds margin around the avatar when spaced is set - class: ori-size-radius_* type: Radius description: zero · xs · sm · md · lg · xl · rounded - class: ori-font-size_* type: Font description: scales the initials text with the avatar size - class: ori-avatar__image · ori-avatar__backdrop · ori-avatar__text · ori-avatar__title · ori-avatar__subtitle type: Part description: image / initials fallback / text column / title / subtitle - class: ori-avatar_inline · ori-avatar_titled · ori-avatar_reverse type: Layout description: inline flow · title+subtitle layout · reversed image/text order --- :: ## Image & initials `src` (and any other image attribute) falls through via `$attrs` to the `` element. When no `src` is present — or while the image loads — the initials backdrop is shown instead, computing up to two letters from the first two words of `text`. ::example :ori-avatar{src="https://oriui.vercel.app/image-example.jpg" text="Marcus Tullius Cicero"} :ori-avatar{text="Marcus Tullius Cicero"} :ori-avatar{text="Ada Lovelace"} #vue ```vue ``` #html ```html
Marcus Tullius Cicero
``` :: ## Colors An optional `color` tints the initials backdrop using the standard semantic token. Has no effect when an image is showing. ::example :ori-avatar{color="primary" text="Primary"} :ori-avatar{color="secondary" text="Secondary"} :ori-avatar{color="success" text="Success"} :ori-avatar{color="warn" text="Warn Color"} :ori-avatar{color="danger" text="Danger"} :ori-avatar{color="info" text="Info"} :ori-avatar{color="surface" text="Surface"} :ori-avatar{color="background" text="Background"} #vue ```vue ``` #html ```html
``` :: ## Sizes `xs` → `xxl`. The size drives the box dimensions (`ori-avatar_*`) and the initials scale (`ori-font-size_*`). Default is `lg`. ::example :ori-avatar{size="xs" text="Extra Small"} :ori-avatar{size="sm" text="Small"} :ori-avatar{size="md" text="Medium"} :ori-avatar{size="lg" text="Large"} :ori-avatar{size="xl" text="Extra Large"} :ori-avatar{size="xxl" text="Double Extra"} #vue ```vue ``` #html ```html
``` :: ## Radius From `zero` (square) to the default `rounded` (full pill / circle). ::example :ori-avatar{radius="zero" text="Zero"} :ori-avatar{radius="sm" text="Small"} :ori-avatar{radius="md" text="Medium"} :ori-avatar{radius="lg" text="Large"} :ori-avatar{radius="rounded" text="Rounded"} #vue ```vue ``` #html ```html
``` :: ## With title & subtitle Pass `title` and/or `subtitle` to show a text column beside the image or initials. The root element switches to `max-content` width via `ori-avatar_titled`. ::example :::ori-avatar --- src: https://oriui.vercel.app/image-example.jpg subtitle: statesman · lawyer · writer · orator text: Marcus Tullius Cicero title: Marcus Tullius Cicero --- ::: :::ori-avatar --- color: secondary subtitle: mathematician · first programmer text: Ada Lovelace title: Ada Lovelace --- ::: #vue ```vue ``` #html ```html
Marcus Tullius Cicero
Marcus Tullius Cicero
statesman · lawyer · writer · orator
``` :: ## Reversed `reverse` flips the layout so the text column appears before the image. ::example :::ori-avatar --- reverse: true color: primary subtitle: mathematician text: Ada Lovelace title: Ada Lovelace --- ::: #vue ```vue ``` #html ```html
Ada Lovelace
mathematician
``` :: ## Inline `inline` renders the avatar as `inline-flex` with a small margin, so it flows inside a sentence or a tag list. ::example :::ori-avatar{inline size="xs" text="Ada Lovelace"} ::: :::ori-avatar --- inline: true color: secondary size: xs text: Marcus Tullius --- ::: #vue ```vue ``` #html ```html
``` :: ## Spaced `spaced` adds padding around the avatar via `ori-size-action-space`, useful when the avatar sits inside a container that provides no gap of its own. ::example :::ori-avatar{spaced text="Ada Lovelace"} ::: :::ori-avatar{spaced color="secondary" text="Marcus Tullius"} ::: #vue ```vue ``` #html ```html
``` :: ## Common patterns A user-list row and a comment header — the everyday compositions. ::example :::ori-avatar --- src: https://oriui.vercel.app/image-example.jpg subtitle: Admin text: Marcus Tullius Cicero title: Marcus Tullius Cicero --- ::: :::ori-avatar --- color: secondary subtitle: Editor text: Ada Lovelace title: Ada Lovelace --- ::: :::ori-avatar --- color: surface subtitle: Guest text: Unknown User title: Unknown User --- ::: #vue ```vue
``` #html ```html
  • Marcus Tullius Cicero
    Marcus Tullius Cicero
    Admin
``` :: ## Accessibility The accessibility contract holds across every layer — the standalone classes and the Vue component render the same attributes. - The `` `alt` is always set — to the `text` prop value, or `''` (empty, decorative) when `text` is omitted. Do not pass a separate `alt` via `$attrs`; it will be overridden. - The initials backdrop is `aria-hidden="true"` — it is a visual fallback, not semantic content. - The `` is revealed only after the `load` event fires (`v-show`), preventing a flash of a broken-image icon while the initials are still visible. - There is no interactive behaviour; the component has no keyboard contract of its own. Wrap it in a ` ``` #html ```html 3 ``` :: ## Common patterns A notification bell, an avatar with a status pip, and a tab bar with unread counts. ::example :badge-demo :ori-badge{color="info" content="New" variant="tonal"} #vue ```vue Messages ``` #html ```html 12
Messages 5 ``` :: ## Accessibility The accessibility contract holds across every layer — the standalone classes and the Vue component render the same attributes. - A badge with visible text content (`content`) conveys that text to assistive technology directly. - A pure dot badge — or an empty badge with no `label` — is marked `aria-hidden="true"` because it carries no information for screen readers. - Supply a `label` whenever the badge conveys meaning that a sighted user can read but a screen reader cannot (e.g. a dot meaning "online", or a floating count with no textual fallback nearby). - A floating count that visually annotates an already-labeled control (e.g. "Inbox (3 unread)") should carry `aria-label` on the badge element or be hidden with `aria-hidden="true"` if the parent label already includes the count. - The badge is a `` — it has no interactive role and no keyboard contract of its own. When a badge and its anchor together form an interactive control, apply focus management and ARIA to the outer interactive element. | Attribute | Condition | Value | | -------------------- | ----------------------------------------------------- | ----------- | | `aria-hidden="true"` | `dot` with no `label`, or empty badge with no `label` | `"true"` | | `aria-label` | `label` prop is set | label value | ## Framework API The props, events, and slots of the **Vue** component. The standalone CSS layer has no component API — its surface is the [classes](https://oriui.vercel.app/#classes) above. (Svelte bindings are planned.) ### Props | Prop | Type | Default | Description | | ---------- | ----------------- | ----------- | ----------------------------------------------------------------------------------------------------------------- | | `color` | `ThemeColor` | `'primary'` | Semantic color role: `primary` · `secondary` · `success` · `warn` · `danger` · `info` · `surface` · `background`. | | `content` | `string | number` | — | Text or number to display. Numbers are capped to `max+` when `max` is set. | | `dot` | `boolean` | `false` | Collapses the badge to a small filled circle; content is hidden. | | `floating` | `boolean` | `false` | Positions the badge in the top-end corner of the default-slot anchor (requires a default slot child). | | `label` | `string` | — | Accessible name (`aria-label`). Required for dot badges or empty badges that convey meaning to sighted users. | | `max` | `number` | — | When `content` is a number and exceeds `max`, the display value is capped to `{max}+`. | | `radius` | `RadiusSize` | `'rounded'` | Corner radius: `zero` · `xs` · `sm` · `md` · `lg` · `xl` · `rounded`. | | `variant` | `Variant` | `'fill'` | Visual style: `fill` · `tonal` · `outline` · `text` · `plain`. | ### Events & attributes OriBadge declares **no custom events**. It sets `inheritAttrs: false` and applies `v-bind="$attrs"` directly to the inner `.ori-badge` element — so `id`, `class`, `data-*`, and event listeners always land on the badge element regardless of whether a default slot (the anchor wrapper) is present. ### Slots | Slot | Description | | --------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `default` | The element the badge floats over. When provided, the component wraps both the slot content and the badge in an `ori-badge-anchor` span. When absent, the badge renders standalone. | | `content` | Custom badge content, replacing the capped `content` value (`{max}+`) that renders by default. Only renders when not a `dot`. A badge that uses this slot is kept in the a11y tree (not decorative). | # Button A styled, accessible button. Dynamic state is expressed through real attributes — `disabled` becomes a true `disabled` (or `aria-disabled` for link buttons), `loading` sets `aria-busy`, and it ships a visible `:focus-visible` ring. The examples are organised by **layer**: the [class reference](https://oriui.vercel.app/#classes) is the standalone **`@oriui/css`** layer, and the [Framework API](https://oriui.vercel.app/#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 button is a block class plus single-class token utilities — one class repoints one token, no base class needed. The Vue props in [Framework API](https://oriui.vercel.app/#framework-api) map 1:1 to these. ::class-table --- rows: - class: ori-button type: Block description: Required base class. - class: ori-variant_* type: Style description: fill · tonal · outline · text · plain - class: ori-color_* type: Color description: primary · secondary · success · warn · danger · info · surface - class: ori-button_* (size) type: Size description: xs · sm · md · lg · xl · xxl - class: ori-font-size_* type: Font description: xs · sm · md · lg · xl · xxl (scales the label) - class: ori-size-radius_* type: Radius description: zero · xs · sm · md · lg · xl · rounded - class: ori-button_fluid · ori-button_icon type: Layout description: full-width · icon-only - class: ori-button__icon · ori-button__text type: Part description: icon / label elements - class: disabled · aria-busy · data-active type: State description: real attributes, not classes --- :: The non-fill variants (`tonal` / `outline` / `text` / `plain`) paint the label with the AA-safe `--ori-color-text` tone rather than the raw role; `fill` keeps `--ori-color-on` for its solid background — see [Design tokens](https://oriui.vercel.app/guides/design-tokens#text-the-on-surface-foreground). ## Variants Five visual styles, all driven by the `ori-variant_*` single-class token. ::example :ori-button{text="Fill" variant="fill"} :ori-button{text="Tonal" variant="tonal"} :ori-button{text="Outline" variant="outline"} :ori-button{text="Text" variant="text"} :ori-button{text="Plain" variant="plain"} #vue ```vue ``` #html ```html ``` :: ## Colors Every semantic role. `surface` / `background` are also available for neutral buttons. ::example :ori-button{color="primary" text="primary"} :ori-button{color="secondary" text="secondary"} :ori-button{color="success" text="success"} :ori-button{color="warn" text="warn"} :ori-button{color="danger" text="danger"} :ori-button{color="info" text="info"} #vue ```vue ``` #html ```html ``` :: Variant × color compose freely — e.g. a tonal danger button: ::example :ori-button{color="danger" text="Delete" variant="tonal"} :ori-button{color="success" text="Confirm" variant="outline"} :ori-button{color="info" text="Note" variant="text"} #vue ```vue ``` #html ```html ``` :: ## Sizes `xs` → `xxl`. The size sugar `ori-button_*` drives the height; `ori-font-size_*` scales the label. ::example :ori-button{size="xs" text="xs"} :ori-button{size="sm" text="sm"} :ori-button{size="md" text="md"} :ori-button{size="lg" text="lg"} :ori-button{size="xl" text="xl"} #vue ```vue ``` #html ```html ``` :: ## Radius From `zero` to the default `rounded` (pill). ::example :ori-button{radius="zero" text="zero"} :ori-button{radius="sm" text="sm"} :ori-button{radius="md" text="md"} :ori-button{radius="lg" text="lg"} :ori-button{radius="rounded" text="rounded"} #vue ```vue ``` #html ```html ``` :: ## With icon Pass an SVG path to `icon`. `iconPosition` places it. For an icon-only square, pass `icon` **without**`text` (give it an `aria-label`) — icon mode is triggered by the explicit `icon` prop, so a slot-only or text button is never forced into a square. ::example :ori-button{icon="M11 13H5v-2h6V5h2v6h6v2h-6v6h-2z" text="Add"} :ori-button{icon="M12 4l-1.41 1.41L16.17 11H4v2h12.17l-5.58 5.59L12 20l8-8z" icon-position="right" text="Next"} :ori-button{ariaLabel="Add" icon="M11 13H5v-2h6V5h2v6h6v2h-6v6h-2z" variant="tonal"} #vue ```vue ``` #html ```html ``` :: ## Loading `loading` swaps the icon for a spinner, sets `aria-busy="true"`, and blocks interaction. ::example :::ori-button{loading text="Saving"} ::: :::ori-button{loading text="Saving" variant="tonal"} ::: :::ori-button --- loading: true ariaLabel: Saving icon: M11 13H5v-2h6V5h2v6h6v2h-6v6h-2z --- ::: #vue ```vue ``` #html ```html ``` :: ## States `active` paints the pressed look (`data-active`); `disabled` is the real attribute. ::example :::ori-button{active text="Active"} ::: :::ori-button{disabled text="Disabled"} ::: :::ori-button{disabled text="Disabled outline" variant="outline"} ::: #vue ```vue ``` #html ```html ``` :: ## Block `fluid` stretches the button to the full width of its container. ::example :::ori-button{fluid text="Full width"} ::: #vue ```vue ``` #html ```html ``` :: ## Common patterns A confirm / cancel pair and an icon toolbar — the everyday compositions. ::example :ori-button{text="Cancel" variant="text"} :ori-button{text="Save changes" variant="fill"} #vue ```vue
``` #html ```html
``` :: ## Accessibility The accessibility contract holds across every layer — the standalone classes and the Vue component render the same attributes and keyboard behaviour. - Renders a real `
Save changes?
You have unsaved edits. Would you like to save before leaving?
``` :: ## Accessibility The accessibility contract holds across every layer — the standalone classes and the Vue component render the same attributes. - `disabled` maps to `aria-disabled="true"` + `pointer-events: none` — the card is dimmed but remains in the DOM and in the tab order (suitable for progressive-disclosure UIs). - `loading` maps to `aria-busy="true"` — assistive technology announces that the region is updating. Combine with a visible spinner inside the `body` slot when appropriate. - Decorative icons (`prependIcon`, `appendIcon`) are rendered via `` which sets `aria-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-surface` token 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](https://oriui.vercel.app/#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 `
`. ### 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. | # Checkbox A styled, accessible checkbox built on a **real ``** — kept in the DOM (visually hidden over a custom box) so keyboard, focus, and native form submission work for free. The accent color and `:focus-visible` ring are driven by the `ori-color` token; `invalid` sets `aria-invalid`; `disabled` is the native attribute. The examples are organised by **layer**: the [class reference](https://oriui.vercel.app/#classes) is the standalone **`@oriui/css`** layer, and the [Framework API](https://oriui.vercel.app/#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 checkbox is a block class on the `