Skip to content
omaris

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.

Variants

The five MD3 fill styles, in the spec's own order of emphasis, plus link.

<script lang="ts">	import { Button } from 'omaris';</script>​<Button variant="elevated">Elevated</Button><Button variant="filled">Filled</Button><Button variant="tonal">Tonal</Button><Button variant="outlined">Outlined</Button><Button variant="text">Text</Button><Button variant="link">Link</Button>

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-lowest
surface-container-low
surface-container
surface-container-high
surface-container-highest
surface-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.

background
card
primary
primary-container
secondary-container
tertiary
tertiary-container
muted
destructive
success
warning
info
<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.

Monospace digits, italic help
<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.