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 organized 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 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 map 1:1 to these.
| Class | Type | Description |
|---|---|---|
ori-button | Block | Required base class. |
ori-variant_* | Style | solid · soft · outline · text · quiet |
ori-color_* | Color | primary · secondary · success · warning · danger · info · surface |
ori-button_* (size) | Size | inherit · xs · sm · md · lg · xl · xxl |
ori-font-size_* | Font | xs · sm · md · lg · xl · xxl (scales the label) |
ori-size-radius_* | Radius | none · xs · sm · md · lg · xl · full |
ori-button_fluid · ori-button_icon | Layout | full-width · icon-only |
ori-button__icon · ori-button__text | Part | icon / label elements |
disabled · aria-busy · data-active | State | real attributes, not classes |
À la carte: the classes above ship in @oriui/css/components/button.css. Import a foundation
(@oriui/css/base.css or @oriui/css/tokens.css) first — the token utilities (ori-color_*,
ori-size-radius_*, …) live there, not in the component file. The full bundle @oriui/css is the default and
already carries both; see à-la-carte imports.
The non-fill variants (soft / outline / text / quiet) paint the label with the AA-safe
--ori-color-text tone rather than the raw role; solid keeps --ori-color-on for its solid
background — see Design tokens.
Variants
Five visual styles, all driven by the ori-variant_* single-class token.
<button class="ori-button ori-variant_solid ori-color_primary">Solid</button>
<!-- swap the variant: ori-variant_solid → _soft / _outline / _text / _quiet -->
<button class="ori-button ori-variant_outline ori-color_primary">Outline</button>
Colors
Every semantic role. surface / background are also available for neutral buttons.
<button class="ori-button ori-variant_solid ori-color_danger">danger</button>
Variant × color compose freely — e.g. a tonal danger button:
<button class="ori-button ori-variant_soft ori-color_danger">Delete</button>
Sizes
xs → xxl. The size sugar ori-button_* drives the height; ori-font-size_* scales the label.
<button class="ori-button ori-button_sm ori-font-size_sm">sm</button>
<button class="ori-button ori-button_xl ori-font-size_xl">xl</button>
Radius
From none to the default full (pill).
<button class="ori-button ori-size-radius_none">none</button>
With icon
Pass an SVG path to icon. iconPosition places it: start (the default), end, top or bottom —
start and end are logical, so they swap sides in RTL. For an icon-only square, pass icon withoutlabel (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.
<button class="ori-button ori-button_icon ori-variant_soft" aria-label="Add">
<i class="ori-icon" aria-hidden="true"
><svg viewBox="0 0 24 24"><path d="M11 13H5v-2h6V5h2v6h6v2h-6v6h-2z" /></svg
></i>
</button>
Loading
loading swaps the icon for a spinner, sets aria-busy="true", and blocks interaction.
<button class="ori-button" aria-busy="true">
<span class="ori-spinner ori-spinner_inline" role="status" aria-hidden="true"></span>
<span class="ori-button__text">Saving</span>
</button>
States
active and pressed are different things, and the difference is the whole toggle contract.
active paints a forced :active look (data-active) and announces nothing — use it for a transient
highlight. pressed is the toggle state: it renders aria-pressed, so assistive tech reports the
button as on or off, and it paints the pressed affordance (a tint plus an inset ring) on the button itself,
with no toolbar ancestor required. Omit pressed entirely on a plain action button — binding it to false
is a claim that the control is a toggle that happens to be off.
disabled is the real attribute.
<button class="ori-button" data-active>Active</button> <button class="ori-button" disabled>Disabled</button>
Fluid
fluid stretches the button to the full width of its container.
<button class="ori-button ori-button_fluid">Full width</button>
Common patterns
A confirm / cancel pair and an icon toolbar — the everyday compositions.
<div style="display: flex; gap: 0.5rem">
<button class="ori-button ori-variant_text">Cancel</button>
<button class="ori-button ori-variant_solid">Save changes</button>
</div>
Accessibility
The accessibility contract holds across every layer — the standalone classes and the Vue component render the same attributes and keyboard behavior.
- Renders a real
<button type="button">by default;as="a"(or a router link) switches the tag and usesaria-disabled+tabindex="-1"instead of the booleandisabled. loadingsetsaria-busy="true"; the spinner isaria-hidden. An icon-only button needs anaria-label.- Visible
:focus-visibleoutline; state lives in attributes, not classes.
| Key | Action |
|---|---|
Enter | Activates the button. |
Space | Activates the button (native only). |
Framework API
The props, events, slots, and polymorphism of the Vue component. The standalone CSS layer has no component API — its surface is the classes above.
Props
| Prop | Type | Default | Description |
|---|---|---|---|
label | string | — | Visible button text. A labeled (or slotted) button is a normal button, never an icon square. |
variant | 'solid' | 'soft' | 'outline' | 'text' | 'quiet' | 'solid' | Visual style. |
color | ThemeColor | 'primary' | Semantic role: primary · secondary · success · warning · danger · info · surface. |
size | ActionSize | 'md' | Height + label scale (xs–xxl). |
radius | RadiusSize | 'full' | Corner radius (none–full). |
icon | string | — | SVG path for an icon; hidden while loading. icon with no label → an icon-only square (ori-button_icon). |
iconPosition | 'top' | 'bottom' | 'start' | 'end' | 'start' | Icon placement around the label. |
loading | boolean | false | Shows a spinner, sets aria-busy, and blocks interaction. |
disabled | boolean | false | Real disabled (button) or aria-disabled + tabindex="-1" (other tags). |
active | boolean | false | Forced :active LOOK via data-active. Not a toggle state — see pressed. |
pressed | boolean | — | Toggle STATE: renders aria-pressed and the pressed affordance. Omit it on a plain action button. |
fluid | boolean | false | Full-width (block) button. |
as | string | Component | 'button' | Element or component to render (e.g. 'a', a router link). |
Events & attributes
OriButton declares no custom events. It doesn't set inheritAttrs: false, so native listeners
(@click, @focus, …) and attributes (type, aria-label, name, form, …) fall through to the
rendered element — the tag given by as.
Slots
| Slot | Description |
|---|---|
default | Replaces the built-in content (icon + text). Supply your own markup; you own its layout/spacing. |
Polymorphic (as)
Render any tag or component. As a non-<button>, disabled becomes aria-disabled + tabindex="-1"
instead of the boolean attribute.