Distilled interface best practices, condensed from the better-ui,
better-colors, better-layout, better-writing, better-typography,
better-accessibility, and better-interface skills. Self-contained: apply
these directly when those skills are not installed. When they are installed,
invoke the relevant one for depth — this file is the floor, not the ceiling.
Throughout: preserve the project's established tokens, density, motion language, and voice. These rules guide new decisions; they are not a license to restyle what already exists.
- Group with space, not lines. Negative space is the primary grouping tool; separator lines are the last resort. The gap between groups must be at least 2× the gap within a group (8px intra → 16px+ inter), or the grouping reads as noise.
- Align to shared edges. Pick alignment edges and stick to them; every stray edge reads as noise. Use one spacing step per level of subordination.
- Order by importance. The most important content sits near the top and
the leading edge. Think leading/trailing, not left/right; use logical
properties (
padding-inline-start) in localizable layouts. - Controls look interactive. A control needs a background shape, border, or consistent placement zone — never styled identically to static text.
- Hint at hidden content. Progressive disclosure needs a visible cue: let the next carousel item peek 16–32px past the edge, or show a disclosure control. Content hidden with zero cue may as well not exist.
- Breakpoints come from content, not device presets. Hold the expanded layout as long as it genuinely fits; prefer container queries for component-level adaptation. Test the smallest and largest sizes first.
- Plan for text growth. No fixed widths/heights on text containers; let rows wrap. Translations grow unpredictably.
- Content bleeds, controls float. Backgrounds and media may extend to the viewport edge; text and controls stay inside layout margins and safe areas.
- Semantic tokens by role. Every color comes from the token system, named for its role. One color, one meaning: if blue is the link color, don't reuse it decoratively — give the second use a neutral.
- One primary action per view. Fill only the single primary action with the accent; secondary actions stay neutral. Several colored control backgrounds in one view compete and cancel out.
- Respect the existing notation. Don't introduce OKLCH into a hex codebase (or vice versa) for an isolated fix; a consistent token system beats a superior color space applied inconsistently.
- Contrast is measured, not eyeballed. Body text: WCAG 4.5:1 / APCA |Lc| ≥ 75 against its actual background. Check both themes — dark mode is not a mechanical inversion of light; retune and re-measure every foreground/background pair.
- States derive from the base. Hover/selected/disabled come from the base
token programmatically (opacity,
color-mix(), modifier classes), not from parallel hand-picked colors that drift.
- Concentric border radius. Outer radius = inner radius + padding. Equal nested radii is the most common thing that makes UI feel off.
- Optical over geometric alignment. Play icons, asymmetric glyphs, and icon+text buttons need manual nudging when geometric centering looks wrong.
- Shadows for elevation, borders for structure. Fake-depth borders become layered transparent shadows; keep borders that communicate structure or state (dividers, selection, focus).
- Interruptible animation. CSS transitions for interactive state changes
(they can reverse mid-flight); keyframes only for one-shot sequences.
ease-outfor both enters and exits; exits softer and smaller than enters. - Motion restraint. No custom animation on high-frequency interactions — the attention cost repeats every trigger. Motion is never the only feedback channel; every animated change needs a static cue too. Skip entrance animations on initial page load.
- Press feedback:
scale(0.96)on click — never below0.95. - Never
transition: all. Name the exact properties. - Icons: one set, one stroke weight, sized to the text they sit beside;
currentColorwith states from CSS — never separate assets per state. Outline is the default; fill marks active.
- Verb-first buttons. Labels name the specific action: "Save draft", "Delete project" — never "OK!"/"Yes" on consequential actions. Confirmation buttons repeat the consequence so the dialog is answerable without the body text.
- One flow vocabulary. Pick "Continue" or "Next" and keep it through the flow; alternating synonyms implies different behavior.
- Errors say how to fix, next to where it broke. "Choose a password with at least 8 characters", inline at the field — not "Invalid password", not "Oops! Something went wrong", no blame, no exclamation marks.
- Empty states point forward. Say what this place is and offer the next action ("No projects yet … Create a project"), never a bare "No results."
- Placeholders are examples, not labels. They vanish on input; every field keeps a visible label.
- Toggles label the ON state. "Send read receipts", never a negative that creates a double negative.
- Links describe their destination. "Read the billing docs", never "Click here" or a bare repeated "Learn more".
- One capitalization policy per element type; sentence case is the safer default. "Save Changes" beside "Discard changes" reads as sloppiness.
- i18n-safe strings. Full templated strings with real pluralization — never concatenate fragments around variables; word order changes per language. Match the device verb: tap (touch), click (pointer), select (both).
- Tone flexes with stakes: warm in success/onboarding, neutral in settings, calm and plain in errors and destructive confirmations — zero playfulness where data or money is at risk.
- Every interactive element is keyboard-reachable, in a logical tab order,
with a visible focus state (
:focus-visible). - Icon-only controls get an accessible name (
aria-label); decorative icons are hidden from the accessibility tree. - Hit areas ≥ 44×44px (or per the platform's standard), even when the visual glyph is smaller; adjacent expanded hit areas must not overlap.
- Every form field has a visible, programmatically associated label; errors
are announced (
aria-invalid, live region), not just colored. - Color is never the only signal — pair it with an icon, label, or weight change.
- Respect
prefers-reduced-motion: gate non-essential animation behind it.
- All font families, sizes, and weights come from the project's type scale — never inline.
- Body line length ≈ 45–75 characters; set
max-widthinchwhen needed. - Numbers that align vertically (tables, timers, prices) use tabular figures
(
font-variant-numeric: tabular-nums). - Truncation (
line-clamp) must never hide the only path to critical content; prefer wrapping. - Minimum 16px font size on mobile inputs (prevents iOS zoom-on-focus).
Before calling any UI change done, walk every state it touches: default, hover, focus, active, disabled, loading, empty, error, overflowing content, narrow viewport, and both themes. Most UI defects live in the states nobody rendered during development.