Semantic first
role=alert, details/summary, <dialog>, button[aria-pressed]. The platform's elements and states are the component API — no data-slot hooks, no wrapper soup.
Mò is a visual language for HTML you already write: sensible defaults you can change, extend, or remove completely. Your CSS always has the final word — and everything on this page is a live component, styled by the very tokens it describes.
THE PRINCIPLES
role=alert, details/summary, <dialog>, button[aria-pressed]. The platform's elements and states are the component API — no data-slot hooks, no wrapper soup.
One --radius resquares the whole library. One --ring recolors every focus state. You re-theme a system, not a pile of selectors.
All library CSS lives in @layer. Any unlayered rule you write wins — regardless of load order. The cascade is the API.
One stylesheet, one script, no framework lock-in. Copy two files into any stack from 2019 or later and you are done.
SEMANTIC FIRST
We style the platform, not replace it. Same HTML, different meaning — components adapt to their context automatically.
<button>Real button: :focus-visible, Enter/Space, disabled — Mò only paints it.<dialog>Native modal: focus trap, Esc to close, ::backdrop. No portal, no aria-hidden juggling.<details>/<summary>Native disclosure state drives the accordion — works before JavaScript arrives.role="alert"Form messages announce themselves; Mò styles the element the platform already understands.button[aria-pressed]A toggle is a pressed button, not a different component. State stays queryable.<label for=…>Inputs ship with real labels. The <label> is the click target, the reader's name, and the docs example.TOKENS, NOT OVERRIDES
Every component reads the same semantic tokens. Override once on an ancestor — the whole system follows.
That panel sets one token: --radius: 0rem. Four components follow.
LAYERS LOSE ON PURPOSE
The library ships inside @layer mo. Unlayered author CSS beats layered CSS in the cascade — even a one-line rule, even loaded first. Try beating it below.
Result
Try it: edit the CSS — the cascade updates live.
1/* the library — layered, loses */2@layer mo { button { padding: 0.5rem 1rem; } }3 4/* you — unlayered, wins. Always. */5button { padding: 0.75rem 1.25rem; }
ZERO DEPENDENCIES
Mò is two files and whatever HTML you already have. No build step, no runtime peers, no version of React to negotiate with.
Convinced? Install it in under a minute →