Skip to content

Components

Workspace

Command palette

Actions

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

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

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 Next.js
<button type="button" className="or-btn or-btn--primary">
  <Icon name="save" size={16} />
  Save changes
</button>
<button type="button" className="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" className="or-btn or-btn--primary" disabled aria-busy="true">
  <Icon name="loader-circle" size={16} className="or-spin" />
  Saving…
</button>

Segmented control

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

States

Selected
Focus
Usage in Next.js
<Seg
  label={t('settings.theme.label')}
  value={theme}
  options={THEMES.map((value) => ({ value, label: t(`settings.theme.${value}`) }))}
  onChange={setTheme}
/>

Menu

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

States

Closed
Open
Usage in Next.js
<Menu
  trigger={({ open, toggle }) => (
    <button type="button" className="or-btn or-btn--ghost" aria-haspopup="true" aria-expanded={open} onClick={toggle}>
      Account
    </button>
  )}
>
  <Link className="or-menu__item" href="/profile">Profile</Link>
  <div className="or-menu__sep" role="separator" />
  <button type="button" className="or-menu__item" onClick={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 Next.js
{/* A filter is a URL: each option is a link to the list with it toggled. */}
<FilterMenu path={path} query={query} keep={keep} field="status" label={t('table.status')} values={filterValues('orderStatus')} />

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 Next.js
<span className="or-badge or-badge--success">
  <span className="or-badge__dot" />
  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 Next.js
<span className="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 Next.js
{/* Initials, never a photograph: a face is a licence problem and a request. */}
<span className="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 Next.js
<span className="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 Next.js
<div className="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 Next.js
// Server actions answer in place: the island shows the result inline
// rather than in a toast, so it is still there after a redirect.
<p className="or-alert or-alert--success" role="status">{t('form.saved')}</p>

Skeleton

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

Variants

Text
Value
Block
Usage in Next.js
<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 Next.js
<EmptyState icon="search" title={t('table.noResults')} body={t('table.noResultsBody')}>
  <button type="button" className="or-btn" onClick={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 Next.js
<FormBar
  dirty={form.dirty}
  saving={save.isPending}
  errors={form.errorCount}
  onSave={() => form.submit(() => save.mutate())}
  onDiscard={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 Next.js
<ConfirmDialog
  open={guard.pending !== null}
  title={t('form.unsaved')}
  body={t('form.unsavedBody')}
  confirmLabel={t('form.leave')}
  onConfirm={guard.leave}
  onCancel={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 Next.js
<Field label={t('settings.general.organisation')} error={form.errors.organisation}>
  {({ id, describedBy, invalid }) => (
    <TextInput
      id={id}
      describedBy={describedBy}
      invalid={invalid}
      field="organisation"
      value={form.values.organisation}
      onChange={(value) => form.set('organisation', value)}
      onBlur={() => form.blur('organisation')}
    />
  )}
</Field>

Select

One value from a short, fixed list.

States

Enabled

That value is not allowed

Error
Disabled
Focus

Sizes

Regular
Small
Usage in Next.js
<Field label={t('table.status')} error={form.errors.status}>
  {({ id, describedBy, invalid }) => (
    <SelectInput id={id} describedBy={describedBy} invalid={invalid} field="status" value={form.values.status} options={STATUS_OPTIONS} onChange={(value) => form.set('status', value)} />
  )}
</Field>

Text area

Several lines of plain text.

See it on a screen

States

Enabled

Required

Error
Disabled
Focus
Usage in Next.js
<textarea className="or-textarea" rows="4" id="bio" aria-describedby="bio-hint"></textarea>
<p className="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 Next.js
{/* Selection is client state; the rows around it are server-rendered. */}
<HeaderCheck label={t('table.selectPage')} />
<RowCheck id={order.id} label={t('table.selectRow', { label: order.number })} />

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 Next.js
<SwitchField
  field="productUpdates"
  label={t('settings.notifications.productUpdates')}
  checked={form.values.productUpdates}
  onChange={(value) => form.set('productUpdates', value)}
/>

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 Next.js
<Combobox
  id={id}
  describedBy={describedBy}
  field="tags"
  invalid={invalid}
  values={form.values.tags}
  options={options('tag')}
  onChange={(values) => form.set('tags', values)}
/>

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
MonTueWedThuFriSatSun
Open

Required

Error
Focus
Usage in Next.js
<DateField
  id={id}
  describedBy={describedBy}
  field="publishAt"
  value={form.values.publishAt}
  today={TODAY}
  onChange={(value) => form.set('publishAt', value)}
/>

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 Next.js
<FileField
  id={id}
  describedBy={describedBy}
  field="images"
  files={form.values.images}
  onChange={(files) => form.set('images', files)}
  onReject={(failure) => 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 Next.js
<RichText
  id={id}
  describedBy={describedBy}
  label={t('product.descriptionIn', { language: t(`language.${locale}`) })}
  value={form.values.description[locale]}
  onChange={(html) => form.set(`description.${locale}`, html)}
/>

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 Next.js
<CodeInput code={code} legend={t('auth.verify.legend')} error={form.errors.code} onChange={setCode} />

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 Next.js
<StrengthMeter password={form.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 Next.js
<div className="or-tabs" role="tablist" aria-label={t('settings.title')} onKeyDown={onKeyDown}>
  {SETTINGS_TABS.map((name) => (
    <Link key={name} className="or-tabs__tab" role="tab" href={`/settings?tab=${name}`}
      aria-selected={name === tab} tabIndex={name === tab ? 0 : -1}>
      {t(`settings.tabs.${name}`)}
    </Link>
  ))}
</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 Next.js
{/* Links rather than buttons: every page is a URL the server renders. */}
<Link className="or-pager__page" href={hrefFor(path, query, { page: page + 1 }, keep)} aria-label={t('table.nextPage')}>
  <Icon name="chevron-right" size={16} />
</Link>

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 Next.js
{/* The selection is client state; the action is a server action. */}
<BulkBar>
  <BulkActions />
</BulkBar>

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 Next.js
<section className="or-card">
  <div className="or-card__head">
    <h3>Recent orders</h3>
    <a className="or-btn or-btn--ghost or-btn--sm" href="orders.html">View all</a>
  </div>
  <div className="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 Next.js
<div className="or-card">
  <div className="or-stat">
    <span className="or-stat__label">Revenue</span>
    <span className="or-stat__value">€48,290.00</span>
    <span className="or-stat__foot"><span className="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 Next.js
{/* Sorting is a link to the same list, sorted: the server renders it. */}
<th scope="col" aria-sort={sort} className="or-num">
  <Link className="or-table__sort" href={hrefFor(path, nextSort(query, 'totalCents'), {}, keep)}>
    {t('table.total')}
    <Icon name={ARROW[sort]} size={16} />
  </Link>
</th>

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 Next.js
<ol className="or-timeline">
  <li className="or-timeline__item">
    <span className="or-timeline__mark" aria-hidden="true">
      <Icon name="shopping-cart" size={16} />
    </span>
    <div className="or-timeline__body">
      <span>Placed order #10482</span>
      <span className="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 Next.js
<dl className="or-facts">
  <div className="or-facts__row">
    <dt>Email</dt>
    <dd><a href="mailto:maya@example.com">maya@example.com</a></dd>
  </div>
</dl>