UI design system
Aurral’s interface puts the music first. It uses compact, neutral surfaces, clear focus and disabled states, and images only where they help. The theme tokens and shared controls in the frontend define the rules. This page explains how to use them.
Run the UI checks
Section titled “Run the UI checks”Run npm run lint:ui from the repository root to check stylesheets and JSX. Run npm run lint to include the UI checks with the other lint rules.
The UI checks are part of the frontend lint step, so CI reports violations through the root lint command.
Use Aurral theme tokens
Section titled “Use Aurral theme tokens”Add global design tokens to frontend/src/index.css. The CSS checker reads token names from that file, so do not copy the token list into another config.
Use an existing --aurral-* token for theme colors, surfaces, borders, rings, shadows, spacing, and radii. CSS and JSX inline styles cannot use literal colors for Aurral theme styling. Use var(--aurral-text) instead of #171717, for example.
The checker fails on literal colors and on undefined --aurral-* references in CSS and JSX. Use the spacing, radius, and shadow tokens when their roles fit. The checker cannot tell whether a local size should use a token, so check those values against the component’s layout.
Name a new token for its role. Use light-dark() when the role needs different values in light and dark themes. Keep local variables when they derive from Aurral tokens or hold runtime values such as dimensions and progress.
Artwork can use colors extracted from the artwork. Keep those values tied to artwork content. Do not use artwork colors for Aurral controls or page chrome. The checker allows the fallback used by the discover playlist artwork treatment.
The stylesheet owns Aurral’s own light and dark colors. Other themes are stored as a few input colors, and frontend/src/utils/theme.js generates the color roles from them with contrast targets and writes them over the stylesheet values. A token outside those roles keeps Aurral’s value in every theme, so derive a new color token from a role token when it should follow the theme. The opt-in Match album art setting is the one exception to the artwork rule above. It feeds the cover color through the same contrast checks, so components should not apply artwork colors to controls themselves.
Reuse shared controls
Section titled “Reuse shared controls”Use .btn with the existing variant classes for standard actions:
<button type="button" className="btn btn-primary"> Save</button>Use TooltipButton for icon-only actions. Give it a label, and include .btn with the button variants:
<TooltipButton label="Refresh" className="btn btn-ghost btn-icon" onClick={refresh}> <RefreshCw aria-hidden="true" /></TooltipButton>Use Tooltip for a tooltip on another element. A Tooltip opens on hover and on keyboard focus, but not on a mouse click that moves focus. ESLint rejects native title tooltips on HTML elements. Keep iframe titles, because they name the frame for assistive technology.
<Tooltip content={fullTrackName}> <span className="track-title">{fullTrackName}</span></Tooltip>Use AddActionButton for actions that add an artist or album to the Library. It includes the standard button classes, the loading state, and the active manager’s label. Pass items to open a menu of add choices instead.
Keep a component-specific control class when the control has a dedicated layout or interaction. The button rule checks bare buttons and classes in the .btn-* family. The theme-token checks still apply to custom CSS and inline styles.
Menu items and switches have different roles. Use role="menuitem" or role="switch" only when the control has that behavior.
Use SectionRail on long pages with several titled sections. Render it as the first child of the page’s content column and point it at the sections. It finds them in the page, so sections that appear later or only in some setups are picked up. It shows only when the column has a left margin wide enough for it, and never on touch screens.
const pageRef = useRef(null);
<div ref={pageRef} className="profile-page"> <SectionRail containerRef={pageRef} sectionSelector=".profile-settings__section" titleSelector=".settings-page__section-title" descriptionSelector=".settings-page__section-note" label="Profile sections" /> {sections}</div>Add a narrow exception
Section titled “Add a narrow exception”Add an entry to frontend/eslint-rules/shared-control-exceptions.json only when an existing control must keep a different pattern. Each entry needs a unique id, a source file, one exact class, class expression, or role, and a reason.
The checker matches exceptions by both file and selector. It does not support file or directory-wide ignores. Add a rule test for each exception.
Review the visual result
Section titled “Review the visual result”The linter checks token use and a few shared controls. It cannot judge spacing, visual hierarchy, interaction states, keyboard flow, responsive behavior, or whether a new component fits Aurral’s existing patterns. Review those details against the current interface and style references when you change UI.