RomM v2: Tokens, Theming & Visual Language

SkillMedia

Guides your agent to style frontend components using design tokens, CSS variables, and light/dark themes.

Use RomM v2: Tokens, Theming & Visual Language in Claude, ChatGPT or Ahel Desktop

Free. Sign in, add RomM v2: Tokens, Theming & Visual Language and connect your AI. About a minute.

Also: Claude Code · Cursor · Codex

Then ask your AI: use the RomM v2: Tokens, Theming & Visual Language skill

Details

Instructions available. Your AI can read the instructions. Execution depends on the setup they require.

Add ahel to your AI once: Claude, ChatGPT, Cursor, Claude Code or Codex. Then ask it to use this.

RomM v2: Tokens, Theming & Visual LanguageStart free
About this skill

Theming, design tokens, colors, and visual language in the RomM v2 frontend. Use when styling v2 components, picking colors, adding/using CSS variables, working with light/dark themes, or whenever you'd reach for a hex/rgba literal. Covers the token pipeline (src/v2/tokens/index.ts → build:tokens →

What this skill tells your AI

The instructions your AI receives, as published by rommapp/romm in .claude/skills/frontend-v2-theming/SKILL.md and read by ahel’s review.

Tokens are the only source of truth for theming. Zero hex/rgba() literals in v2 components. If a value is missing, add a token. Every component must work in both v2-dark and v2-light.


Token pipeline

src/v2/tokens/index.ts is the source. It feeds two consumers:

  • src/v2/styles/tokens.css: generated by scripts/build-tokens.ts (npm run build:tokens, hooked into predev/prebuild). Do not hand-edit. This is how the vast majority of tokens are consumed: var(--r-color-...) in CSS.
  • Direct JS/TS imports of named exports (colorCanvas, colorCoverArt, layout, …) for the few cases needing a token value in JavaScript: baking colors into an SVG string (utils/covers), canvas/QR backgrounds (Player/Ruffle.vue, ShowQRCodeDialog), and the virtualiser's pixel math (Gallery/listColumns reading layout).

v2 has no Vuetify theme of its own, and no Vuetify at all. tokens.css emits a palette block per theme under .r-v2.r-v2-dark / .r-v2.r-v2-light; RomM.vue toggles those classes on <html>. v2 surfaces never read Vuetify's runtime theme (src/plugins/vuetify.ts serves v1 only).

Adding a new token

  1. Add it to src/v2/tokens/index.ts with a semantic, role-based name (--r-color-danger, not --r-color-red).
  2. Provide both dark and light values.
  3. Consume via var(--r-...) (CSS) or the named export (JS).
  4. Run npm run build:tokens to regenerate tokens.css.
  5. If the JS→CSS variable name needs an exception (e.g. --r-nav-h), add an entry to NAME_OVERRIDES in the generator.

Where the scope classes live, and why <html>

.r-v2, .r-v2-dark, .r-v2-light go on <html> (RomM.vue toggles them whenever uiVersion or the active theme changes). The overlay primitives (RDialog, RMenu, RTooltip) <Teleport to="body">, landing outside the app root. Only <html> covers both the regular tree and the teleports; without it, overlays lose their tokens.

Diagnostics: when var(--r-color-...) resolves to nothing on an overlay

  1. Check RomM.vue's watch on documentElement.classList (load-bearing).
  2. Check that the teleport target is attached to the document. <html> is the document root, so any in-document target inherits the scope; a detached node resolves no var(--r-*) at all.
  3. Never "fix" it by swapping the token for a hex literal; that hides the bug and breaks the dual theme.

Visual language

  1. Single visual vocabulary. Every surface (dialog, menu, popover, card, toolbar) reads as a sibling: same blur, curvature, depth. No standalone "dialog look" vs "menu look".
  2. Canonical references when designing: ask the user before consulting https://mockup.thebirdcage.tv/. They decide whether the mockup or existing primitives take priority.
  3. State semantics are shared across primitives (don't reinvent per component):
    • hover (neutral, or brand-tinted on selected rows)
    • selected/checked (--r-color-brand-primary)
    • active/favorite (--r-color-fav)
    • focus (modality-gated; visible only on key/pad, see frontend-v2-input)
    • busy/pending · disabled
  4. Implementation gotchas:
    • Every dialog goes through RDialog; every menu through RMenu; every tooltip through RTooltip. Each owns its teleport and positioning, so don't hand-roll a parallel surface. The scrim and the reference-counted body scroll lock (lib/overlays/bodyScrollLock.ts) belong to RDialog and RDrawer only; menus and tooltips have neither.

Color-literal policy: zero exceptions

Outside src/v2/tokens/index.ts (the source-of-truth TS module) and the generated src/v2/styles/tokens.css, no hex or rgba() literals exist anywhere in v2. Everything previously "excepted" is now a token or a color-mix:

  • Cover-overlay glass → --r-color-overlay-* (fixed dark glass; never theme-flips).
  • Cover artwork placeholder & shimmer → --r-color-cover-placeholder, --r-color-cover-placeholder-bright.
  • Panel / tooltip / shimmer-sweep → --r-color-panel, --r-color-panel-border, --r-color-tooltip-bg, --r-color-shimmer-sweep.
  • Backdrop scrims (global.css) → color-mix(in srgb, var(--r-color-bg) X%, transparent).
  • Status tints → color-mix(in srgb, var(--r-color-status-base-{success,warning,danger,info}) X%, transparent).
  • Brand-tinted backgrounds (selected rows, focus rings) → color-mix(in srgb, var(--r-color-brand-primary) X%, transparent).
  • Black/white shadows → color-mix(in srgb, black X%, transparent) (CSS named color, not a hex literal).
  • Metadata-provider chips → --r-color-provider-*. Player canvas → --r-color-canvas-bg, --r-color-canvas-bg-deep.
  • Emphasis pill (always-white-on-dark "Play" CTA over cover art) → --r-color-overlay-emphasis-bg/-fg/-bg-hover.

If a literal would otherwise be needed, the answer is: add a token (steps above), then consume via var(--r-color-...) or the named export.

romm/no-color-literal (ESLint, frontend/eslint-plugin-romm/) flags hex and color-function literals in SFC <style> blocks under src/v2. It does not see .css files, stories' inline styles, or colors in TS strings.

Style conventions

  • Scoped <style> by default; unscoped only for teleport overrides.
  • BEM-ish class names: .feature__element--modifier. Prefixes: .r-v2-... for app-shell surfaces outside components; .r-... for globally shared utilities/tokens.
  • No utility-class framework (no Tailwind, no Vuetify): layout is plain CSS in the component's scoped block, with tokens (var(--r-space-*), var(--r-radius-*)) for every value that has one.

Signals

GitHub stars
13k
Forks
747
Last commit
Oct 2026
Advanced
Item type
skill
Key
frontend-v2-theming
Source
github.com/rommapp/romm