Skip to content

Components

Workspace

Command palette

↑↓ to move · ↵ to choose · Esc to close

Every component, with its variants, sizes and states. Change the appearance here and every sample follows.

Language

Theme

Density

Layout

Direction

Mirrors the layout, the hard shadow and the directional icons. Choosing a language sets this; change it here to see either direction in any language.

Actions

Button

One primary action per screen. Loading is disabled and busy, and says what it is doing.

Variants

Default
Primary
Danger
Ghost

Sizes

Regular
Small
Icon
Small icon

States

Enabled
Disabled
Loading
Focus
Usage in Nuxt
<button type="button" class="or-btn or-btn--primary">
  <Icon name="save" :size="16" />
  Save changes
</button>
<button type="button" class="or-btn or-btn--icon or-btn--sm" aria-label="Notifications">
  <Icon name="bell" :size="16" />
</button>
<!-- Loading: disabled and busy, the label says what is happening. -->
<button type="button" class="or-btn or-btn--primary" disabled aria-busy="true">
  <Icon name="loader-circle" :size="16" class="or-spin" />
  Saving…
</button>

Segmented control

Three or four choices that apply at once, shown side by side.

States

Selected
Focus
Usage in Nuxt
<Seg
  :label="t('settings.theme.label')"
  :model-value="preferences.theme"
  :options="THEMES.map((value) => ({ value, label: t(`settings.theme.${value}`) }))"
  @update:model-value="preferences.setTheme"
/>

Menu

A list of actions behind a button. Escape and a click outside close it.

States

Closed
Open
Usage in Nuxt
<Menu>
  <template #trigger="{ open, toggle }">
    <button type="button" class="or-btn or-btn--ghost" aria-haspopup="true" :aria-expanded="open" @click="toggle">
      Account
    </button>
  </template>
  <NuxtLinkLocale class="or-menu__item" to="/profile">Profile</NuxtLinkLocale>
  <div class="or-menu__sep" role="separator" />
  <button type="button" class="or-menu__item" @click="signOut">Sign out</button>
</Menu>

Filter chip

Opens the values of one column. The count says how many are chosen.

See it on a screen

States

Closed
With a count
Open
Focus
Usage in Nuxt
<TableFilterMenu
  field="status"
  :label="t('table.status')"
  :values="ORDER_STATUSES"
  :query="table.query.value"
  :label-of="(value) => format.label('orderStatus', value)"
  @toggle="table.filter"
/>

Status

Badge

A status. The dot is a second channel, so the colour is never the only signal.

Tones

Packing
Neutral
Paid
Success
Pending
Warning
Refunded
Danger
Shipped
Information
Archived
Muted
Usage in Nuxt
<span class="or-badge or-badge--success">
  <span class="or-badge__dot"></span>
  Paid
</span>

Change

Up, down, or nothing to compare with. Never "+100%" from zero.

Variants

+12.4%
Up
-3.1%
Down
no comparison
No comparison
Usage in Nuxt
<span class="or-delta or-delta--up">
  <Icon name="trending-up" :size="16" />
  +12.4%
</span>

Avatar

Initials on a palette colour. A person keeps the same avatar in every theme.

Colours

MO
Cream
TR
Sunset
ID
Mustard
KM
Cactus
LV
Sky
SH
Rose

Sizes

LV
Small
LV
Regular
LV
Large
Usage in Nuxt
<!-- Initials, never a photograph: a face is a licence problem and a request. -->
<span class="or-avatar or-avatar--sky or-avatar--lg">LV</span>

Tag

A chosen value that can be removed on its own.

States

Gifts
Enabled
Gifts
Focus
Usage in Nuxt
<span class="or-tag">
  Gifts
  <button type="button" aria-label="Remove Gifts">
    <Icon name="x" :size="16" />
  </button>
</span>

Feedback

Alert

A message that belongs to the screen and stays until the situation changes.

Tones

Prices include VAT for customers in the EU.
Information
Your payout of €2,480.00 is on its way.
Success
Three products are out of stock.
Warning
Danger
Usage in Nuxt
<div class="or-alert or-alert--warning" role="status">
  <Icon name="triangle-alert" :size="18" />
  <div>Three products are out of stock.</div>
</div>

Toast

The result of something the reader just did. It leaves on its own.

Tones

Export started. It will be ready in a minute.
Information
Saved
Success
Nothing was saved.
Danger
Usage in Nuxt
const toasts = useToasts()
toasts.push(t('form.saved'), 'success')

Skeleton

The shape of what is loading, so the page can be read before the numbers land.

Variants

Text
Value
Block
Usage in Nuxt
<Skeleton variant="value" />
<Skeleton width="40%" />

Empty state

Says why there is nothing here and offers the way out.

See it on a screen

Variants

Nothing matches those filters

Try a different search, or clear the filters to see everything again.

With an action
Usage in Nuxt
<EmptyState icon="search" :title="t('table.noResults')" :body="t('table.noResultsBody')">
  <button type="button" class="or-btn" @click="table.clearFilters">{{ t('table.clearFilters') }}</button>
</EmptyState>

Form bar

What is unsaved and the two things to do about it, stuck to the bottom of the form.

See it on a screen

States

Saved

Saved

Unsaved changes

Unsaved

2 fields need attention

Error

Unsaved changes

Loading
Usage in Nuxt
<FormBar
  :dirty="form.dirty.value"
  :saving="save.isPending.value"
  :errors="form.errorCount.value"
  @save="form.submit(() => save.mutate())"
  @discard="form.discard()"
/>

Confirm dialog

Stands between the reader and losing their work. Escape is the safe answer.

Variants

Unsaved changes

Leaving this screen now loses them.

Confirmation
Usage in Nuxt
<FormConfirmDialog
  :open="guard.pending.value !== null"
  :title="t('form.unsaved')"
  :body="t('form.unsavedBody')"
  :confirm-label="t('form.leave')"
  @confirm="guard.leave"
  @cancel="guard.stay"
/>

Inputs

Text field

A label, the control, and an error or hint the control points at.

See it on a screen

States

Enabled
Empty

Shown on invoices and in emails.

With a hint

Required

Error
Disabled
Focus

Sizes

Regular
Small
Search
Usage in Nuxt
<FormField v-slot="slot" :label="t('settings.general.organisation')" :error="form.errors.value.organisation">
  <FormTextInput
    v-bind="slot"
    field="organisation"
    :model-value="form.values.value.organisation"
    @update:model-value="form.set('organisation', $event)"
    @blur="form.blur('organisation')"
  />
</FormField>

Select

One value from a short, fixed list.

States

Enabled

That value is not allowed

Error
Disabled
Focus

Sizes

Regular
Small
Usage in Nuxt
<FormField v-slot="slot" :label="t('table.status')" :error="form.errors.value.status">
  <FormSelectInput v-bind="slot" field="status" :model-value="form.values.value.status" :options="STATUS_OPTIONS" @update:model-value="form.set('status', $event)" />
</FormField>

Text area

Several lines of plain text.

See it on a screen

States

Enabled

Required

Error
Disabled
Focus
Usage in Nuxt
<textarea class="or-textarea" rows="4" id="bio" aria-describedby="bio-hint"></textarea>
<p class="or-hint" id="bio-hint">280 characters left</p>

Checkbox

Three states, because a header checkbox has to say "some of this page".

See it on a screen

States

Unchecked
Checked
Some
Disabled
Focus
Usage in Nuxt
<TableCheck
  :checked="selection.selected.value.has(order.id)"
  :label="t('table.selectRow', { label: order.number })"
  @change="selection.toggle(order.id)"
/>

Switch

A yes or no that takes effect when the form is saved.

See it on a screen

States

Off
On

Once a day, before 9 a.m.

With a hint
Disabled
Focus
Usage in Nuxt
<FormSwitchField
  field="productUpdates"
  :label="t('settings.notifications.productUpdates')"
  :model-value="form.values.value.productUpdates"
  @update:model-value="form.set('productUpdates', $event)"
/>

Typeahead

Several values from a long list, found by typing.

See it on a screen

States

Empty
GiftsHome
Filled
Home
Open
Error
Home
Focus
Usage in Nuxt
<FormCombobox
  v-bind="slot"
  field="tags"
  :model-value="form.values.value.tags"
  :options="options('tag')"
  @update:model-value="form.set('tags', $event)"
/>

Date picker

Typed as YYYY-MM-DD or chosen from a month grid the arrow keys can walk.

See it on a screen

States

Closed
February 2026
Open

Required

Error
Focus
Usage in Nuxt
<FormDateField
  v-bind="slot"
  field="publishAt"
  :model-value="form.values.value.publishAt"
  :today="TODAY"
  @update:model-value="form.set('publishAt', $event)"
/>

File upload

A drop zone with a real file input inside, so it works from a keyboard.

See it on a screen

States

Drop images here

PNG or JPG, up to 2 MB each, 6 at most

Empty

Drop images here

PNG or JPG, up to 2 MB each, 6 at most

Dragging over

Drop images here

PNG or JPG, up to 2 MB each, 6 at most

  • lamp-front.jpg412 kB
  • lamp-detail.png1280 kB
Filled
Usage in Nuxt
<FormFileField
  v-bind="slot"
  field="images"
  :model-value="form.values.value.images"
  @update:model-value="form.set('images', $event)"
  @reject="(failure) => toasts.push(t(failure.key, failure.values), 'danger')"
/>

Rich text

Bold, italic, lists and links. Pasted text arrives clean.

See it on a screen

States

Head of Operations. Runs the Lisbon warehouse and answers the difficult emails.

Filled
Empty

Head of Operations. Runs the Lisbon warehouse and answers the difficult emails.

Focus
Usage in Nuxt
<FormRichText
  v-bind="slot"
  :label="t('product.descriptionIn', { language: t(`language.${locale}`) })"
  :model-value="form.values.value.description[locale]"
  @update:model-value="form.set(`description.${locale}`, $event)"
/>

One-time code

Six boxes that behave like one field: typing moves on, pasting fills them all.

See it on a screen

States

Verification code
Empty
Verification code
Filled
Verification code
Error
Usage in Nuxt
<AuthCodeInput :code="code" :legend="t('auth.verify.legend')" :error="errors.code" @update:code="onCode" />

Password strength

Advice, never a gate. Only the length can refuse a password.

See it on a screen

Variants

Too short
Too short
Weak
Weak
Fair
Fair
Good
Good
Strong
Strong
Usage in Nuxt
<AuthStrengthMeter :password="values.password" />

Navigation

Tabs

Parts of one screen. The tab is in the URL, and the arrow keys move along the row.

See it on a screen

States

Selected
Focus
Usage in Nuxt
<div class="or-tabs" role="tablist" :aria-label="t('settings.title')" @keydown="onKeyDown">
  <NuxtLink v-for="name in SETTINGS_TABS" :key="name" class="or-tabs__tab" role="tab"
    :to="{ path: localePath('/settings'), query: { tab: name } }" :aria-selected="name === tab" :tabindex="name === tab ? 0 : -1">
    {{ t(`settings.tabs.${name}`) }}
  </NuxtLink>
</div>

Pagination

Where the reader is in a long list, and the rows per page.

See it on a screen

States

Showing 1–25 of 287
First page
Showing 126–150 of 287
A middle page
Showing 276–287 of 287
Last page
Usage in Nuxt
<TablePager :page="orders.data.value" @page="(page) => table.patch({ page })" @per-page="(perPage) => table.patch({ perPage })" />

Bulk action bar

Appears with the first selected row, in the flow rather than over the table.

See it on a screen

Variants

3 selected
With a selection
Usage in Nuxt
<TableBulkBar :count="selection.selected.value.size" @clear="selection.clear">
  <button type="button" class="or-btn or-btn--sm" @click="bulk.mutate({ status: 'shipped', fulfilment: 'fulfilled' })">{{ t('orders.bulkMarkShipped') }}</button>
</TableBulkBar>

Data display

Card

The container for one subject, with an optional head.

Variants

Recent orders

A card holds one subject: a list, a chart, a form section.

Padded

Recent orders

OrderCustomerStatus
#10482Maya OkaforPaid€184.50
#10481Tomás ReyesPending€62.00
#10480Ines DuarteRefunded€129.90
Flush
Usage in Nuxt
<section class="or-card">
  <div class="or-card__head">
    <h3>Recent orders</h3>
    <a class="or-btn or-btn--ghost or-btn--sm" href="orders.html">View all</a>
  </div>
  <div class="or-card__body">…</div>
  <!-- or-card__body--flush when the body is a table that runs to the edges -->
</section>

Stat

One figure, what it measures, and how it moved.

See it on a screen

States

Revenue€48,290.00+12.4%
Filled
Revenue
Loading
Usage in Nuxt
<div class="or-card">
  <div class="or-stat">
    <span class="or-stat__label">Revenue</span>
    <span class="or-stat__value">€48,290.00</span>
    <span class="or-stat__foot"><span class="or-delta or-delta--up">+12.4%</span></span>
  </div>
</div>

Table

Sortable headers, selectable rows, and numbers aligned on the decimal.

See it on a screen

States

OrderCustomerStatus
#10482Maya OkaforPaid€184.50
#10481Tomás ReyesPending€62.00
#10480Ines DuarteRefunded€129.90
Enabled
OrderCustomerStatus
#10482Maya OkaforPaid€184.50
#10481Tomás ReyesPending€62.00
#10480Ines DuarteRefunded€129.90
Selected
Usage in Nuxt
<TableSelectAll :rows="rows" :selected="selection.selected.value" @toggle="selection.toggleAll(rows)" />
<TableSortHeader field="totalCents" :label="t('table.total')" :query="table.query.value" numeric @sort="table.sortBy" />

Timeline

What one account did, newest first, as an ordered list.

See it on a screen

Variants

  1. Placed order #10482Feb 18, 2026, 10:24 AM
  2. Opened the spring newsletterFeb 11, 2026, 4:02 PM
  3. Created an accountNov 3, 2025, 9:15 AM
History
Usage in Nuxt
<ol class="or-timeline">
  <li class="or-timeline__item">
    <span class="or-timeline__mark" aria-hidden="true">
      <Icon name="shopping-cart" :size="16" />
    </span>
    <div class="or-timeline__body">
      <span>Placed order #10482</span>
      <span class="or-timeline__when">18 Feb 2026, 10:24</span>
    </div>
  </li>
</ol>

Description list

Label and value pairs, paired in the markup and not only in the layout.

See it on a screen

Variants

Country
Portugal
Lifetime spend
€2,489.00
Label and value
Usage in Nuxt
<dl class="or-facts">
  <div class="or-facts__row">
    <dt>Email</dt>
    <dd><a href="mailto:maya@example.com">maya@example.com</a></dd>
  </div>
</dl>