Design guidelines
How to apply ranui on a real screen: choose a role and a state, and let the tokens supply the values.
The rules themselves live in two places, and this page links to them rather than restating them: the
design guide for the method that applies to any product, and the
ranui implementation adapter
for ranui's tokens, tiers and component rules. Both ship in node_modules/ranui/docs/DESIGN.md. The
design system lists every token and its value,
theming covers runtime overrides, and
information architecture covers page structure.
Use when choosing color, spacing, type, depth or motion for a screen built with
<r-*>elements.
Principles
- Clarity before personality. The primary task and the primary action are unmistakable first.
- Reuse existing controls.
r-button,r-input,r-selectandr-modalalready carry form, keyboard and accessibility behavior; restyle them rather than rebuilding them. - Decide by role and state. "What is this text?" has an answer; "what size looks right?" does not. The token for the role supplies the value, and it follows the theme.
- Design every reachable state, and look at the result. The default state is one of eight, and a shadow nobody can see shows up only in a browser.
These are starting points. Depart from one when the screen is better for it, and say why.
Choosing a color
The ladder already decides what hover and active look like; your job is to name the role.
| The element is… | Use |
|---|---|
| A page or surface background | --ran-color-bg / -bg-subtle / -bg-elevated / -bg-muted |
| Under the pointer / being pressed | --ran-color-bg-hover / -bg-active |
| Text | --ran-color-text / -text-secondary / -text-disabled |
| A border | --ran-color-border / -hover / -active |
| The one action the screen is for | --ran-color-primary (+ --ran-color-primary-text on it) |
| A status | --ran-color-success / -warning / -danger |
| A link | --ran-color-link |
Accents have one meaning each. Primary is monochrome, so blue is left for links and the focus ring; green is success, amber warning, red danger. Using red for emphasis leaves nothing to say "danger" with later.
A raw hex, a fallback that does not flip with the theme, or a fallback naming a token that does not
exist breaks silently in dark mode or drops the whole declaration; the adapter's
color rules
explain each, and verify:design catches them in ranui's own source.
Spacing and rhythm
Take gaps from the 4px scale and let the distance carry meaning:
- 8px between elements inside a group.
- 16px between groups.
- 24–32px between regions.
A smaller gap means a closer relationship, so one off-scale gap is what breaks the rhythm. Give each gap one owner: count a child's margin and the parent's padding before adding a third space between them.
Choosing type
Ask what role the text plays (heading, label, copy, button, mono); the font, size, weight and line height follow from the type scale. Genuinely one-off text, such as a gesture flash on a video, is better off with its own component token than forced into the nearest role.
Depth: shadow and stacking
Pick the shadow tier by what the element is: an in-flow surface, a floating overlay or a blocking dialog. An overlay that falls back to the card tier looks pinned to the page.
Embedding ranui overlays in your own chrome. The z-index ladder
starts at 1000, so a portaled overlay clears ordinary page chrome on its own. A position: fixed
overlay that stays inside its own shadow DOM (r-modal's dialog) escapes only as far as its nearest
ancestor stacking context. If you wrap embedded content in something that creates one
(isolation, opacity < 1, transform, filter, will-change), raise that wrapper only while an
overlay is actually open:
.embed {
isolation: isolate; /* cheap: no z-index of its own, so nothing is promoted */
}
/* Escalate only while a real overlay is open — never "just in case" */
.embed:has(r-modal[open]),
.embed:has(r-modal[closing]) {
position: relative;
z-index: 100;
}A permanent z-index on the wrapper lifts everything inside it, static content included, over your
sticky header for its whole scroll lifetime; that shipped on this site once. Match closing as well
as open: the mask keeps painting through its closing transition.
Motion
Frequent feedback (hover, focus, press) is immediate. When motion explains a change, feedback stays
within 0–100ms, entering takes 250–400ms and decelerates, and leaving takes 150–200ms and accelerates.
Keep it interruptible and respect prefers-reduced-motion.
Never let a color property transition. CSS cannot tell why a color changed, so a transition
on background-color, color, border-color, box-shadow, fill or stroke also fires when the
theme flips: every element fades at its own pace while the page has already switched. Animate
transform and opacity instead. transition: all and a bare transition: 0.2s include color too.
States and content
Every reachable state is part of the design: hover, active, focus, disabled, loading, empty, error.
Hover maps to bg-hover / border-hover, active to bg-active, focus to the focus ring. Nothing that
does not click looks clickable: r-card reacts to hover only with hoverable.
Copy is part of the system too:
- Buttons name the outcome, with the object when context does not supply it: "Delete member". A clearly named dialog may say "Delete"; avoid a generic "OK".
- Errors say what happened, then how to fix it. ✅ "Build failed: the bundle exceeds the size limit. Reduce it or raise the limit." ❌ "Operation failed, please try again."
- Confirmations state the change, not the success, and appear only once it is confirmed. ✅ "Project deleted" ❌ "Operation successful".
- Let context remove redundancy: a dialog titled "Delete project" does not need a button labelled "Delete project permanently, forever".
Accessibility
- Meet WCAG AA contrast for text against its background.
- Never signal state with color alone: pair it with an icon, a label or text.
- Every interactive element keeps a visible focus ring (
--ran-focus-ring, oroutline: 2px solid var(--ran-color-focus); outline-offset: 2px). - Everything is reachable by keyboard.
- Respect
prefers-reduced-motionandprefers-color-scheme.
Mouse and touch, narrow and wide
Neither input nor viewport is the secondary target.
- Drags, sliders and gestures use Pointer Events, with
touch-action: noneon the exact drag surface.touch-action: nonewith no pointer handler behind it is a broken control. - A hover-only affordance needs a tap fallback.
trigger="hover"onr-selectorr-popoverbecomes a click on touch devices; anything you build should do the same. - Prefer viewport-relative sizing (
min(560px, calc(100vw - 32px)),clamp()) to inventing a breakpoint: ranui has no breakpoint token, so each hard breakpoint is a number someone maintains. - Never hide the only way to do something on mobile. Reflow it instead.
- A measured position is correct only until the next reflow. Anything derived from
getBoundingClientRect()goes stale on resize, on container reflow and, for a portaled panel, on scroll. Loading at a narrow width does not exercise resizing into it, which is where this breaks.
What the library enforces mechanically
pnpm -F ranui verify:design checks nine rules in ranui's own source: color fallbacks that do not
flip, raw color literals, the spacing and sizing scales, mouse-only drags, :host rules that break
hidden, fallbacks naming undeclared tokens, components searching their own shadow tree, and shadow
trees built outside the constructor. A line that breaks a rule on purpose says so with
design-ok: <rule> <reason>. The checks cover only the configured source roots; an application that
uses ranui runs its own checks and looks at its own pages.
Design examples
Schematics derived from ranui foundation tokens, not live components.
Keep rows aligned
When a row gains extra content, keep its text aligned with other rows and its actions in the same column.
Coordinate nested corners
When both inner and outer boundaries are needed, subtract the inset from the outer radius: 16 − 4 = 12. The curves then share a center and look evenly spaced.
--ran-radius-lg − --ran-space-1 = --ran-radius-mdMake small icons easier to click
The 16px icon sits inside a 32px click target, making it easier to use. Choose the target size for mouse or touch input, keeping neighboring targets separate.
--ran-size-1 · --ran-size-7Evidence and acceptance
The guide's verifying a change says what to check in a real browser and what to report: what you observed, what you only inferred, and what you could not check. An agent's review is not the user's acceptance.