mcpField
T
theme-colors

theme-colors

github.com/saschb2bDesign & UI2236 installsUpdated 2w ago
Repository

About theme-colors

A color-theming skill for UI development that directs components to use semantic theme roles instead of scattered hardcoded values. It explains role definitions, transparency handling, allowed exceptions, and a codebase audit workflow.

Key features

  1. 01Use theme roles for component colors
  2. 02Apply transparency with alpha()
  3. 03Bind gradients to theme values
  4. 04Audit and replace hardcoded colors
  5. 05Identify allowed color-literal exceptions

Use cases

  1. 01Remove hardcoded colors from components
  2. 02Build palettes that support dark mode
  3. 03Review gradients and transparent overlays
  4. 04Extend themes for brands or data visualization

Install

Copy the command and run it in a terminal.

$npx skills add https://github.com/saschb2b/skills --skill theme-colors

After installing, type/theme-colorsin Claude Code to confirm it loaded.

Bundled files

References and scripts installed along with SKILL.md.

CARD.manifest.json
CARD.md
CARD.md.sigstore
CARD.svg
permissions.yaml

Details

From the project README

Theme Colors

Rule

Every color comes from the theme. No hex literals, no rgba() strings, no scattered values.

A theme defines colors by role (primary, secondary, error, surface, text) once, in one place. Components reference the role, not the value. Brand changes update one file. Dark mode is the same theme with different values. Components derive their own hover, focus, and pressed transparencies from the role color via the alpha() helper.

The most common failure mode in LLM-generated UI is hex codes sprinkled through component files (color: "#3B82F6", bgcolor: "#1E293B", background: "rgba(255,255,255,0.5)"). Training corpora are dominated by tutorial code that hardcodes colors. The defaults skew wrong.

The vocabulary, 10 roles

RoleMeaningUsed for
primaryMain brand colorMain buttons, active tabs, key highlights
secondaryComplementary accentSwitches, FABs, selection controls
errorSomething wrong or destructiveError messages, delete buttons
warningCaution, not yet an errorWarning banners, risky-action confirmations
infoNeutral information, no urgencyTooltips, info badges, help text
successPositive outcomeSuccess notifications, completion
background.defaultPage background ("the desk")The app's base background
background.paperElevated surface ("paper on the desk")Cards, dialogs, dropdowns, menus, drawers
text.primaryMain text, high emphasisHeadings, body text
text.secondaryDe-emphasized textCaptions, timestamps, helper text

Each role auto-derives .light, .dark, and .contrastText. Use the defaults; override only with a specific contrast or design reason.

Default behavior when writing UI

Do this:

<Box sx={{ color: "text.primary", bgcolor: "background.paper" }}>
  <Button color="primary">Save</Button>
</Box>

Not this:

<Box sx={{ color: "#1E293B", bgcolor: "#FFFFFF" }}>
  <Button sx={{ bgcolor: "#3B82F6" }}>Save</Button>
</Box>

For transparency, use alpha() against a theme color, never a literal rgba():

import { alpha } from "@mui/material";

sx={(theme) => ({
  bgcolor: alpha(theme.palette.secondary.main, 0.08),
})}

For gradients and any other string-typed CSS that needs theme values, use the sx callback form so the theme is available:

sx={(theme) => ({
  background: `linear-gradient(135deg, ${theme.palette.primary.main} 0%, ${theme.palette.accent.alt} 100%)`,
})}

Three places where a hex literal IS appropriate

  1. The theme definition itself (createTheme({ palette: { primary: { main: "#6366F1" } } })). The theme file is where hex values live, on purpose.
  2. Custom palette extensions that do not fit a standard role: brand-specific accents, data-viz palettes with semantic meaning (e.g. green-up / red-down in finance). Add them to the theme as named entries, then reference by name from components.
  3. Inline SVG fragments where the color belongs to an external asset (logos, icons not from the icon library). Keep the literal inside the asset itself, never scatter the same hex across other components.

Anything else gets a theme token.

Audit workflow

  1. Grep the codebase for color literals:
    grep -rEn '(#[0-9a-fA-F]{3,8}|rgba?\(|hsla?\(|oklch\()' src/ app/ components/
    
  2. Classify each match.
    • Inside the theme file or a SVG attribute, leave it.
    • Anywhere else, convert to a theme token.
  3. For rgba(R, G, B, A), identify the role the RGB approximates and replace with alpha(theme.palette.<role>.<variant>, A). Use the sx callback when needed.
  4. For gradients with literal stops, move them into an sx={(theme) => ...} callback and interpolate theme.palette.<role>.<variant>.
  5. For named colors used inline (color: "blue", bg: "white"), replace with the closest theme role.
  6. For "magic" colors not in the theme, do not add an inline override. Either map to the closest existing role, or add a named entry to the theme and reference it from there.

Rules of thumb

  • Theme colors must be opaque. Transparency lives in the component via alpha(), not in the palette definition. A pre-diluted theme color stacks with hover/focus/pressed overlays and looks wrong.
  • Resist manually overriding .light / .dark / .contrastText until you have a concrete contrast or design issue. The defaults are derived with perceptual contrast.
  • The 10-role set is enough for most apps. Adding a 30-role palette turns the theme back into a spreadsheet of hex values with extra steps.

Source

Based on Why Developers Keep Asking for Primary Instead of Blue.

Comments

Markdown supported