oriUI

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 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.

ClassTypeDescription
ori-buttonBlockRequired base class.
ori-variant_*Stylefill · tonal · outline · text · plain
ori-color_*Colorprimary · secondary · success · warn · danger · info · surface
ori-button_* (size)Sizexs · sm · md · lg · xl · xxl
ori-font-size_*Fontxs · sm · md · lg · xl · xxl (scales the label)
ori-size-radius_*Radiuszero · xs · sm · md · lg · xl · rounded
ori-button_fluid · ori-button_iconLayoutfull-width · icon-only
ori-button__icon · ori-button__textParticon / label elements
disabled · aria-busy · data-activeStatereal 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.

Variants

Five visual styles, all driven by the ori-variant_* single-class token.

html
<button class="ori-button ori-variant_fill ori-color_primary">Fill</button>
<!-- swap the variant: ori-variant_fill → _tonal / _outline / _text / _plain -->
<button class="ori-button ori-variant_outline ori-color_primary">Outline</button>
vue
<OriButton text="Fill" variant="fill" />
<OriButton text="Tonal" variant="tonal" />
<OriButton text="Outline" variant="outline" />
<OriButton text="Text" variant="text" />
<OriButton text="Plain" variant="plain" />

Colors

Every semantic role. surface / background are also available for neutral buttons.

html
<button class="ori-button ori-variant_fill ori-color_danger">danger</button>
vue
<OriButton text="primary" color="primary" />
<OriButton text="danger" color="danger" />
<OriButton text="info" color="info" />

Variant × color compose freely — e.g. a tonal danger button:

html
<button class="ori-button ori-variant_tonal ori-color_danger">Delete</button>
vue
<OriButton text="Delete" variant="tonal" color="danger" />
<OriButton text="Confirm" variant="outline" color="success" />

Sizes

xsxxl. The size sugar ori-button_* drives the height; ori-font-size_* scales the label.

html
<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>
vue
<OriButton text="sm" size="sm" />
<OriButton text="xl" size="xl" />

Radius

From zero to the default rounded (pill).

html
<button class="ori-button ori-size-radius_zero">zero</button>
vue
<OriButton text="zero" radius="zero" />
<OriButton text="rounded" radius="rounded" />

With icon

Pass an SVG path to icon. iconPosition places it. For an icon-only square, pass icon withouttext (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.

html
<button class="ori-button ori-button_icon ori-variant_tonal" 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>
vue
<OriButton text="Add" icon="M11 13H5v-2h6V5h2v6h6v2h-6v6h-2z" />
<OriButton text="Next" icon="M12 4l-1.41…" iconPosition="right" />
<!-- icon-only square: pass `icon` with no `text`, add an accessible name -->
<OriButton icon="M11 13H5v-2h6V5h2v6h6v2h-6v6h-2z" aria-label="Add" variant="tonal" />

Loading

loading swaps the icon for a spinner, sets aria-busy="true", and blocks interaction.

html
<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>
vue
<OriButton text="Saving" loading />
<OriButton icon="…" aria-label="Saving" loading />

States

active paints the pressed look (data-active); disabled is the real attribute.

html
<button class="ori-button" data-active>Active</button> <button class="ori-button" disabled>Disabled</button>
vue
<OriButton text="Active" active />
<OriButton text="Disabled" disabled />

Block

fluid stretches the button to the full width of its container.

html
<button class="ori-button ori-button_fluid">Full width</button>
vue
<OriButton text="Full width" fluid />

Common patterns

A confirm / cancel pair and an icon toolbar — the everyday compositions.

html
<div style="display: flex; gap: 0.5rem">
    <button class="ori-button ori-variant_text">Cancel</button>
    <button class="ori-button ori-variant_fill">Save changes</button>
</div>
vue
<div style="display: flex; gap: 0.5rem">
    <OriButton text="Cancel" variant="text" />
    <OriButton text="Save changes" variant="fill" />
</div>

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 <button type="button"> by default; as="a" (or a router link) switches the tag and uses aria-disabled + tabindex="-1" instead of the boolean disabled.
  • loading sets aria-busy="true"; the spinner is aria-hidden. An icon-only button needs an aria-label.
  • Visible :focus-visible outline; state lives in attributes, not classes.
KeyAction
EnterActivates the button.
SpaceActivates 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. (Svelte bindings are planned.)

Props

PropTypeDefaultDescription
textstringLabel. A text (or slotted) button is a normal button, never an icon square.
variant'fill' | 'tonal' | 'outline' | 'text' | 'plain''fill'Visual style.
colorThemeColor'primary'Semantic role: primary · secondary · success · warn · danger · info · surface.
sizeActionSize'md'Height + label scale (xsxxl).
radiusRadiusSize'rounded'Corner radius (zerorounded).
iconstringSVG path for an icon; hidden while loading. icon with no text → an icon-only square (ori-button_icon).
iconPosition'left' | 'right' | 'top' | 'bottom''left'Icon placement around the label.
loadingbooleanfalseShows a spinner, sets aria-busy, and blocks interaction.
disabledbooleanfalseReal disabled (button) or aria-disabled + tabindex="-1" (other tags).
activebooleanfalsePressed look via data-active.
fluidbooleanfalseFull-width (block) button.
asstring | 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

SlotDescription
defaultReplaces 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.

html
<a href="/docs" class="ori-button ori-variant_outline">Link button</a>
vue
<!-- a real link… -->
<OriButton as="a" href="/docs" text="Link button" variant="outline" />
<!-- …or a router link -->
<OriButton :as="RouterLink" to="/docs" text="Go to docs" />