mica

Custom elements. Native behavior. Nearly no JavaScript.

View source: there is nothing here but HTML and a stylesheet.

<m-vstack> — vertical flow

one
two
three

<m-hstack> — one row; add wrap to allow wrapping

alpha
beta
gamma
delta
epsilon

<m-zstack> — layering; paint order is DOM order

centered layer

badge

Per-layer placement is just place-self on the child — light DOM means the platform is the escape hatch.

<m-grid> — responsive, no media queries

1
2
3
4
5
6

<m-sidecar> — fixed + flexible pair, stacks when narrow

sidecar
content
content
sidecar

<m-switcher> — row ↔ column at a threshold

a
b
c

<m-reel> — horizontal scroll with snap

slide 1
slide 2
slide 3
slide 4
slide 5
slide 6

<m-frame> — hold a ratio, crop media to fit

16:9 (default)

square

4:3

--ratio: 21/9

random photo cropped to 2:1

<m-cover> — center the principal, pin the edges

pinned top

the explicit principal element

pinned bottom ↓

Defaults to min-block-size: 100svh; scaled down here via --min-height. An h1 is principal by default; mark another child with data-principal to override it.

The scales — for eyeballing the ramps (toggle OS dark mode)

Each step should be distinguishable from its neighbors; 6–8 should read as borders on 1–3; 11 should be readable text on 1–3.

1
2
3
4
5
6
7
8
9
10
11
12
1
2
3
4
5
6
7
8
9
10
11
12

Contrast spot-checks: muted text (11) on surface-raised (2) on-accent on accent-9 border (6) on surface (1)

Buttons — native <button>, styled directly

A link dressed as a button

data-variant accepts primary, ghost, or danger; data-size accepts small or large.

Status roles (hue knobs, no scales): danger success warn warn solid

Fields — native inputs, zero classes

Profile Email is required. That doesn't look like an email address.
Billing (disabled fieldset — everything inside silenced)

Checks — drawn controls, native semantics

All three are native <input>s — keyboard, forms, and state stay native. role="switch" gives the track its matching on/off semantics.

Segmented control — native radio group

Leaderboard period

Fused labels around native radios: arrows, keyboard, forms, and AT semantics come from the browser. No script and no ARIA reconstruction.

Stepper — framed sequence controls

Championship quarterfinals

Mica fuses the frame; native buttons provide focus, keyboard, and disabled behavior. Labels, events, routing, and value changes stay in the application.

Visually hidden — announced, not seen

There is a hidden sentence at the end of this paragraph. This text is off-screen but present in the accessibility tree. Sighted readers see nothing; a screen reader reads it in place.

Skip to content — the skip link before this dash is hidden until it takes focus. Tab to it, or press Tab from the address bar, and it appears.

The same rule hides the radios inside m-segmented, where the label is the visible control.

Badge — quiet metadata

Default Primary Success Warning Danger 8

Informational only. For an interactive chip, compose a real button rather than making a badge pretend to be a control.

Avatar — initials, native images, optional deterministic paint

AN AL AN AL GH MI Photo avatar example Akonwi Ngohakonwi@example.com

Initials and direct img children need no script. avatar.js locally derives color and features from the blobatar seed; contained opts out of the full-bleed treatment.

Skeleton — quiet loading placeholders

Loading profile

Each empty box reserves consumer-defined space while the surrounding region owns loading state and announcements. Reduced-motion users get a static fill.

Stat grid — semantic summaries in a fused frame

Position
3rdEastern Conference
Played
24League matches
Wins
1458% win rate
Goal difference
+18
Next match
vs. Northbridge

A native description list keeps each label associated with its value. Intrinsic columns wrap without a media query; .stat remains the individual-item styling hook.

Progress & meter — thin bars, status-graded meter

Content — tables, lists, code. Zero classes.

ElementLevelJS
m-vstackCSS-onlynone
dialognative behaviornone
m-fieldJS-enhancedone module
Hover a row. Borders are horizontal only; numerals are tabular.
  • unordered
  • list with
  • muted markers
  1. ordered
  2. works
  3. too
mica
a mineral that cleaves into thin sheets
book
what a raw chunk of mica is called

Inline code gets a chip; press K for nothing.

<m-vstack gap="lg">
  <button data-variant="primary">Submit</button>
</m-vstack>
Delete the stylesheet and it still works — that is the demo.

The hr above is a thematic break — Markdown emits one for every ---, so prose-heavy pages get them constantly. Mica replaces the browser's beveled groove with a quiet rule.

Dialog — modal & drawer, zero JS

Open, focus trap, Esc, backdrop, close — browser + invoker commands. No script anywhere in this section.

Confirm

Focus is trapped, Esc closes, the backdrop dims — all the browser's.

Body content sits between header and footer. The dialog owns the vertical spacing, like a stack.

Drawer

Same native dialog, dressed as a drawer. Esc still closes it.

Accordion — details + summary, exclusive via name

Is it accessible?

Yes. Keyboard, semantics, and exclusivity are the browser's — the shared name makes the group exclusive.

Is it styled?

Yes. Border-per-item, rotating chevron, animated height where the platform supports it.

Is it animated?

Where interpolate-size is supported the height eases; elsewhere it opens instantly. Zero JS either way.

Popover, menu & tooltip — zero JS

Anchored panel Light dismiss, Esc, and toggling are the browser's. Anchored to its invoker where anchor positioning exists; centered elsewhere.

<m-toast> — display only; queueing is JS-enhanced

With toast.js: open toasts stack upward, auto-dismiss (hover to pause), and toast() spawns them from code. Position is the app-level token --toast-position — the picker sets it on :root; every open toast obeys.

Heads up popover="manual": survives light dismiss; dismissed by its X. Saved Your changes are safe.

Tabs — m-tabs + tabs.js (JS-enhanced)

Account settings panel. Without tabs.js, all three panels are visible in order — complete content, honestly degraded.

Password panel. Arrow keys move between tabs (roving tabindex, automatic activation).

Team panel.

The underline variant uses the same behavior. In a narrow rail, labels stay on one line, the nav scrolls horizontally, and edge markers show whichever direction has more tabs:

The rail is constrained to 20rem; tabs keep their intrinsic width and the overflow scrolls.

Attacking leaders.

Passing leaders.

Defending leaders.

Goalkeeping leaders.

Combobox — m-combobox + combobox.js (JS-enhanced)

Type to filter; arrows + Enter to pick. Without the module this is a native datalist — functional autocomplete, just unstylable.

The preset — bare prose, no mica tags, no demo styles

This section is plain HTML flow content. The spacing between these paragraphs, the heading margins, and the line length wrapping come from @layer mica.preset — constructive defaults, not a blank slate.

Tab to this link to see the tokenized focus ring — buttons (above) get the same ring from the same tokens.

A following heading gets breathing room above

Because it isn't a first child. Inside a stack, all of this rhythm yields to gap — the stack owns spacing.

Escape hatch — arbitrary values via custom properties

<m-vstack style="--gap: 2.75rem"> — attributes and custom properties are the same mechanism.

one
two