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>
Badges
Status indicators, solid and soft.
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.
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.
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.
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.
— this is a sample alert message with supporting detail text.
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.
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
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.
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.
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
Pro plan
For growing teams
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.
Order placed
09:14
Payment confirmed
09:16
Out for delivery
Pending
Design
Week 1–2
Build
Week 3–6
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).
- Scrollable row
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.
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…
</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>
Carousel
Dependency-free Alpine carousel; the Widgets phase adds a Swiper-powered variant for touch/gesture support.
View code
<div x-data="{ slide: 0, slides: [...] }" class="relative overflow-hidden">
<div class="flex transition-transform" :style="'transform: translateX(' + (-slide * 100) + '%)'">
<template x-for="s in slides"><div class="w-full shrink-0" x-text="s"></div></template>
</div>
</div>
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>
Modal
Invite a teammate
They'll get an email invite to join this workspace.
View code
<div x-data="modal()">
<button @click="show($event)">Open modal</button>
<div x-show="open" x-cloak class="fixed inset-0 flex items-center justify-center">
<div class="absolute inset-0 bg-neutral-950/60" @click="hide()"></div>
<div x-ref="panel" role="dialog" aria-modal="true" @keydown.escape.window="hide()">
Dialog content
</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>