Build
Principles
Five rules that make a screen built from these parts look like one product.
The components agree with each other because they all follow the same five rules. Follow them in the code between the components — the layouts, the one-off elements, the overrides — and the whole screen agrees too. None of this is a style guide to memorise; each rule is one sentence and one habit.
1. Emphasis is hierarchy
One thing on a screen is the thing. It gets the filled treatment; everything else steps down — tonal, then outlined, then text. The same ladder runs through buttons, chips, cards, badges and alerts, which is why a screen with one filled button reads as calm and a screen with five reads as shouting.
The habit: before adding a filled anything, find the one that is already there. If there is one, yours is tonal.
2. Depth comes from surfaces, not lines
A card is not a box with a border. It is a tier of surface — lowest to highest — that reads as closer to the reader without a shadow. Nest tiers to group things; reach for a border or a divider only when the grouping is still unclear.
Surface tiers
The elevation ladder, as background tiers. Higher reads as closer.
surface-container-lowestsurface-container-lowsurface-containersurface-container-highsurface-container-highestsurface-variant<script lang="ts"> // Written out in full: Tailwind scans source text, so a class assembled at // runtime (`bg-{tier}`) would never be generated. const TIERS = [ ['bg-surface-container-lowest', 'surface-container-lowest'], ['bg-surface-container-low', 'surface-container-low'], ['bg-surface-container', 'surface-container'], ['bg-surface-container-high', 'surface-container-high'], ['bg-surface-container-highest', 'surface-container-highest'], ['bg-surface-variant', 'surface-variant'] ];</script><div class="grid w-full grid-cols-[repeat(auto-fill,minmax(11rem,1fr))] gap-3"> {#each TIERS as [cls, name] (name)} <div class="flex min-h-16 flex-col justify-end rounded-shape-md border border-border p-3 {cls}"> <code class="text-label-md text-foreground">{name}</code> </div> {/each}</div> The habit: page bg-background → a panel bg-surface-container-low → a raised card bg-surface-container → a well inside it bg-surface-container-high. Up one tier per level of nesting, and stop at three.
3. Tokens, never values
bg-primary, not a hex. rounded-shape-lg, not rounded-[16px]. shadow-2, not shadow-md. A token follows the theme — the hue, the roundness, dark mode, high contrast, the density slider — and a raw value follows nothing, so it is the one thing on the screen that will look wrong the moment someone changes a setting.
Colour roles
Each swatch is painted with the class written on it.
backgroundcardprimaryprimary-containersecondary-containertertiarytertiary-containermuteddestructivesuccesswarninginfo<script lang="ts"> const ROLES = [ ['bg-background text-foreground', 'background'], ['bg-card text-card-foreground', 'card'], ['bg-primary text-primary-foreground', 'primary'], ['bg-primary-container text-primary-container-foreground', 'primary-container'], ['bg-secondary-container text-secondary-container-foreground', 'secondary-container'], ['bg-tertiary text-tertiary-foreground', 'tertiary'], ['bg-tertiary-container text-tertiary-container-foreground', 'tertiary-container'], ['bg-muted text-muted-foreground', 'muted'], ['bg-destructive text-destructive-foreground', 'destructive'], ['bg-success text-success-foreground', 'success'], ['bg-warning text-warning-foreground', 'warning'], ['bg-info text-info-foreground', 'info'] ];</script><div class="grid w-full grid-cols-[repeat(auto-fill,minmax(11rem,1fr))] gap-3"> {#each ROLES as [cls, name] (name)} <div class="flex min-h-16 flex-col justify-end rounded-shape-md border border-border p-3 {cls}"> <code class="text-label-md">{name}</code> </div> {/each}</div> The habit: every colour is a role and comes as a pair. bg-primary with text-primary-foreground; bg-primary-container with text-primary-container-foreground. Use the pair and the contrast is handled in both themes. The roles are on the Design tokens page.
4. Motion is feedback
Things move because something happened: a button pressed, a panel opened, a card grew to fit new content. Motion that runs on its own is decoration, and decoration is opt-in — the library ships no idle animation, and everything that moves says what motion-reduce does to it.
The habit: transition the property that changed, on an MD3 curve — ease-emphasized for things arriving, ease-standard for state — with motion-reduce:transition-none beside it. If you cannot say what user action the animation answers, delete it.
5. Every part is reachable, so never fork
class styles a component's root and always wins. classes={{ part: '…' }} reaches every inner element by name. Between the two there is no element you cannot restyle, which means there is never a reason to copy a component into your project to change one thing about it.
Classes parts
Anything deeper than the root is a named key on classes.
<script lang="ts"> import { Input } from 'omaris'; let value = $state('42.0000');</script><Input class="w-72 max-w-full" label="Reading" supportingText="Monospace digits, italic help" bind:value classes={{ control: 'font-mono tabular-nums', support: 'italic' }}/> The habit: when an override does not take, it is not the component ignoring you; it is the merge rule — aspect-video does not cancel a height, because they are different groups. Look for the prop the component gives you instead.
What this adds up to
A screen where the primary action is obvious, the grouping is felt rather than drawn, everything follows the theme, nothing moves without a reason, and nothing was forked. That is the whole design system. The component pages say which part to use where; Building a screen says how to put them together.