Component library 30 base components

Base components

Every component below is real markup rendered with the Metaroic token layer — open "View code" under any of them for the exact snippet.

Typography

Headings, body copy, lead paragraphs and inline code.

Eyebrow label

Heading 1

Heading 2

Heading 3

Heading 4

A lead paragraph sits a little larger and lighter, used to introduce a page or section.

Body text is set at 15px for comfortable reading density across dense dashboard pages, with muted text used for secondary detail.

Muted / helper text, e.g. form hints and timestamps.

Inline code looks like npm run dev.

View code
<h1 class="text-4xl font-semibold tracking-tight text-text-primary">Heading 1</h1>
<p class="text-lg text-text-secondary">Lead paragraph</p>
<p class="text-base text-text-primary">Body text</p>
<p class="text-sm text-text-tertiary">Muted text</p>
<code class="rounded bg-surface-sunken px-1.5 py-0.5 font-mono text-[0.85em] text-brand-700">npm run dev</code>

Buttons

Variants, sizes, icon-only and loading states.

View code
<button class="rounded-md bg-brand-600 px-4 py-2 text-sm font-medium text-white shadow-sm hover:bg-brand-700">Primary</button>
<button class="rounded-md border border-surface-border bg-surface-raised px-4 py-2 text-sm font-medium text-text-primary hover:bg-surface-sunken">Outline</button>
<button class="inline-flex items-center gap-2 rounded-md bg-brand-600 px-4 py-2 text-sm font-medium text-white">
  <i data-lucide="plus" class="size-4"></i> With icon
</button>

Badges

Status indicators, solid and soft.

Brand Success Warning Danger Info Neutral
View code
<span class="rounded-full bg-success-50 px-2.5 py-0.5 text-xs font-medium text-success-700">Success</span>

Labels

Removable tag chips for filters, categories and multi-select fields.

Retail Wholesale Priority
View code
<span class="inline-flex items-center gap-1.5 rounded-md border border-surface-border bg-surface-sunken px-2.5 py-1 text-xs font-medium text-text-secondary">
  Retail
  <button aria-label="Remove Retail"><i data-lucide="x" class="size-3"></i></button>
</span>

Avatars

Initials avatars, sizes, status dot and a stacked group.

SM MD LG
JM
AK ZN BM +4
View code
<div class="flex -space-x-2.5">
  <span class="flex size-9 items-center justify-center rounded-full bg-info-500 text-xs font-semibold text-white ring-2 ring-surface-raised">AK</span>
  <span class="flex size-9 items-center justify-center rounded-full bg-neutral-200 text-xs font-semibold text-neutral-700 ring-2 ring-surface-raised">+4</span>
</div>

Cards

Basic, header/footer and stat variants.

Basic card

Plain content block with the default border and radius.

With header

Body content area.

62.4%

Goal completion

View code
<div class="rounded-xl border border-surface-border bg-surface-raised shadow-xs">
  <div class="border-b border-surface-border px-5 py-3">Header</div>
  <div class="px-5 py-4">Body</div>
  <div class="border-t border-surface-border px-5 py-3">Footer</div>
</div>

Alerts

Inline status messages, dismissible.

View code
<div x-data="{ dismissed: false }" x-show="!dismissed" class="flex items-start gap-3 rounded-lg border border-success-200 bg-success-50 p-4 text-sm text-success-800">
  <i data-lucide="check-circle-2" class="mt-0.5 size-4"></i>
  <p class="flex-1">Saved successfully.</p>
  <button @click="dismissed = true" aria-label="Dismiss"><i data-lucide="x" class="size-4"></i></button>
</div>

Accordion

Single or multi-open, animated with the Alpine collapse plugin.

View code
<div x-data="accordion({ open: ['item-1'] })">
  <button @click="toggle('item-1')" :aria-expanded="isOpen('item-1')">Question</button>
  <div x-show="isOpen('item-1')" x-collapse>Answer</div>
</div>

Tabs

Line and pill styles, arrow-key roving focus per WAI-ARIA.

Overview panel content.

Activity panel content.

Settings panel content.

View code
<div x-data="tabs('overview')">
  <div role="tablist" @keydown="onKeydown($event)">
    <button role="tab" :aria-selected="isActive('overview')" @click="select('overview')">Overview</button>
  </div>
  <p x-show="isActive('overview')">Panel</p>
</div>

Pagination

Showing 11–20 of 248

View code
<nav x-data="{ page: 1 }" aria-label="Pagination" class="flex items-center gap-1">
  <button @click="page--" :disabled="page === 1">Prev</button>
  <button :class="page === 1 && 'bg-brand-600 text-white'" @click="page = 1">1</button>
  <button @click="page++">Next</button>
</nav>

Progress

Linear and circular indicators.

Storage used68%
Migration progress32%
View code
<div class="h-2 overflow-hidden rounded-full bg-surface-sunken">
  <div class="h-full rounded-full bg-brand-600" style="width: 68%"></div>
</div>

Spinners

Loading…
View code
<i data-lucide="loader-2" class="size-6 animate-spin text-brand-600"></i>

Tooltips

Pure CSS, shown on hover/focus — no positioning JS needed for a fixed placement.

Tooltip on top Tooltip on the side
View code
<span class="group relative">
  <button aria-describedby="tip"><i data-lucide="info"></i></button>
  <span id="tip" role="tooltip" class="absolute bottom-full opacity-0 transition group-hover:opacity-100 group-focus-within:opacity-100">
    Tooltip text
  </span>
</span>

Popovers

Click-triggered, richer than a tooltip — built on the same dropdown primitive.

Conversion rate

Share of sessions that completed checkout, calculated over a rolling 30-day window.

View code
<div x-data="dropdown()" class="relative inline-block">
  <button @click="toggle()" :aria-expanded="open">What is this?</button>
  <div x-show="open" x-cloak @click.outside="close()" class="absolute mt-2 w-72 rounded-lg border bg-surface-raised p-4 shadow-lg">
    Explanation text
  </div>
</div>

Separators

Section one content.

Section two content.

or
LabelValue
View code
<div class="h-px bg-surface-border"></div>
<span class="h-full w-px bg-surface-border"></span>

Rating

View code
<div x-data="rating({ value: 3 })" role="radiogroup">
  <template x-for="star in stars()">
    <!-- Keep :class on this wrapper, never on the <i data-lucide> itself -->
    <!-- (lucide replaces that element with an <svg>, which drops any -->
    <!-- binding tied to the x-for loop variable). -->
    <button role="radio" :aria-checked="value === star" @click="set(star)" :class="isFilled(star) && 'text-accent-500 [&>svg]:fill-current'">
      <i data-lucide="star"></i>
    </button>
  </template>
</div>

Ribbons

Popular

Pro plan

For growing teams

SALE

Starter plan

For solo builders

View code
<div class="relative overflow-hidden rounded-xl border p-5">
  <span class="absolute end-3 top-3 rounded-full bg-accent-500 px-2.5 py-0.5 text-xs font-semibold text-white">Popular</span>
</div>

Timeline

Vertical and horizontal.

  1. Order placed

    09:14

  2. Payment confirmed

    09:16

  3. Out for delivery

    Pending

  1. Design

    Week 1–2

  2. Build

    Week 3–6

  3. Launch

    Week 7

View code
<ol class="flex flex-col gap-5 border-s border-surface-border ps-4">
  <li class="relative">
    <span class="absolute -start-[1.3rem] top-0.5 size-2.5 rounded-full bg-brand-500 ring-4 ring-surface-raised"></span>
    <p>Order placed</p>
  </li>
</ol>

Stepper

View code
<ol x-data="{ step: 2 }" class="flex items-center">
  <li>
    <span :class="n < step ? 'bg-brand-600 text-white' : 'border text-text-tertiary'">1</span>
  </li>
</ol>

Scroll areas

A fixed-height panel with a themed scrollbar (see main.css).

View code
<div class="h-48 overflow-y-auto rounded-xl border p-4">
  <!-- content taller than 12rem scrolls with the themed scrollbar -->
</div>

Block overlay

Blocks interaction with a card/section while an async action runs.

Table or form content sits here.

Saving changes…
View code
<div class="relative">
  Content
  <div x-show="loading" class="absolute inset-0 flex items-center justify-center bg-surface-raised/80 backdrop-blur-sm">
    <i data-lucide="loader-2" class="animate-spin"></i> Saving&hellip;
  </div>
</div>

Image input

Avatar upload trigger and a drop-zone placeholder (wired to real uploads in Phase 2).

View code
<label class="flex flex-col items-center rounded-xl border-2 border-dashed px-6 py-8 hover:border-brand-400">
  <i data-lucide="upload-cloud"></i>
  <span>Click to upload or drag and drop</span>
  <input type="file" class="sr-only" accept="image/*" />
</label>

Rotating text

Ship your dashboard

View code
<span x-data="{ words: [...], i: 0 }" x-init="setInterval(() => (i = (i + 1) % words.length), 2200)">
  <template x-for="(w, idx) in words">
    <span x-show="i === idx" x-transition x-text="w"></span>
  </template>
</span>

Mega menu

Same dropdown primitive, a wider multi-column panel.

View code
<div x-data="dropdown()">
  <button @click="toggle()">Products</button>
  <div x-show="open" class="grid w-[36rem] grid-cols-3 gap-1">
    <div>Column</div>
  </div>
</div>

Toast

Backed by the global $store.toasts store — the host is included once per page.

View code
<button @click="$store.toasts.push({ title: 'Saved', variant: 'success' })">
  Trigger toast
</button>

Skeleton

Loading placeholder every widget uses before its data resolves.

View code
<div class="animate-pulse rounded-xl border p-5">
  <div class="h-3 w-24 rounded bg-surface-sunken"></div>
</div>