css-theme

Build theming systems with modern CSS. Covers custom properties architecture (primitive, semantic, component tokens), oklch/oklab color spaces, color-mix() for tints and shades, light-dark() for automatic dark mode, @property for typed custom properties, multiple theme support, contrast themes, and forced-colors mode. Use when building a theme, adding dark mode, creating color systems, setting up design tokens, or implementing color schemes.

Best use case

css-theme is best used when you need a repeatable AI agent workflow instead of a one-off prompt.

Build theming systems with modern CSS. Covers custom properties architecture (primitive, semantic, component tokens), oklch/oklab color spaces, color-mix() for tints and shades, light-dark() for automatic dark mode, @property for typed custom properties, multiple theme support, contrast themes, and forced-colors mode. Use when building a theme, adding dark mode, creating color systems, setting up design tokens, or implementing color schemes.

Teams using css-theme should expect a more consistent output, faster repeated execution, less prompt rewriting.

When to use this skill

  • You want a reusable workflow that can be run more than once with consistent structure.

When not to use this skill

  • You only need a quick one-off answer and do not need a reusable workflow.
  • You cannot install or maintain the underlying files, dependencies, or repository context.

Installation

Claude Code / Cursor / Codex

$curl -o ~/.claude/skills/css-theme/SKILL.md --create-dirs "https://raw.githubusercontent.com/Lionad-Morotar/local-tools/main/local-link/skills/css-dev-skills/skills/css-theme/SKILL.md"

Manual Installation

  1. Download SKILL.md from GitHub
  2. Place it in .claude/skills/css-theme/SKILL.md inside your project
  3. Restart your AI agent — it will auto-discover the skill

How css-theme Compares

Feature / Agentcss-themeStandard Approach
Platform SupportNot specifiedLimited / Varies
Context Awareness High Baseline
Installation ComplexityUnknownN/A

Frequently Asked Questions

What does this skill do?

Build theming systems with modern CSS. Covers custom properties architecture (primitive, semantic, component tokens), oklch/oklab color spaces, color-mix() for tints and shades, light-dark() for automatic dark mode, @property for typed custom properties, multiple theme support, contrast themes, and forced-colors mode. Use when building a theme, adding dark mode, creating color systems, setting up design tokens, or implementing color schemes.

Where can I find the source code?

You can find the source code on GitHub using the link provided at the top of the page.

SKILL.md Source

# css-theme — Modern CSS Theming Systems

You are a CSS theming specialist. Your job is to design and implement complete theming systems using modern CSS — no JavaScript theme toggling, no CSS-in-JS, no preprocessor variables. Pure CSS custom properties, `color-mix()`, `light-dark()`, and `@property`.

For the full pattern catalog, see the css-expert skill's [modern-patterns.md](../css-expert/references/modern-patterns.md). For browser support details on `light-dark()`, `@property`, and color functions, see [browser-compat.md](../css-expert/references/browser-compat.md).

## Workflow

```
Theme Build Progress:
- [ ] Step 1: Define primitive color tokens (oklch palette)
- [ ] Step 2: Create semantic token layer
- [ ] Step 3: Wire up light/dark mode
- [ ] Step 4: Add component-level tokens
- [ ] Step 5: Register animated properties with @property
- [ ] Step 6: Add contrast and forced-colors support
- [ ] Step 7: (Optional) Add extra themes beyond light/dark
```

## Step 1: Primitive Color Tokens

Build the raw color palette using `oklch()`. Every color starts here.

```css
:root {
  /* Primary */
  --primary-50: oklch(95% 0.05 250);
  --primary-100: oklch(90% 0.08 250);
  --primary-200: oklch(80% 0.12 250);
  --primary-300: oklch(70% 0.18 250);
  --primary-400: oklch(60% 0.22 250);
  --primary-500: oklch(55% 0.25 250);
  --primary-600: oklch(48% 0.22 250);
  --primary-700: oklch(40% 0.18 250);
  --primary-800: oklch(32% 0.14 250);
  --primary-900: oklch(25% 0.10 250);

  /* Neutral */
  --neutral-0: oklch(100% 0 0);
  --neutral-50: oklch(97% 0.005 250);
  --neutral-100: oklch(93% 0.005 250);
  --neutral-200: oklch(87% 0.01 250);
  --neutral-300: oklch(78% 0.01 250);
  --neutral-400: oklch(65% 0.01 250);
  --neutral-500: oklch(55% 0.01 250);
  --neutral-600: oklch(44% 0.01 250);
  --neutral-700: oklch(35% 0.01 250);
  --neutral-800: oklch(25% 0.01 250);
  --neutral-900: oklch(18% 0.01 250);
  --neutral-1000: oklch(10% 0.005 0);
}
```

### Why oklch?

- **Perceptually uniform**: equal lightness steps look visually equal (unlike HSL)
- **Wide gamut**: access colors outside sRGB on capable displays
- **Predictable**: adjusting L changes brightness without hue shift

### Generating Tints and Shades with color-mix()

Instead of hardcoding every shade, derive them:

```css
:root {
  --primary: oklch(55% 0.25 250);
  --primary-light: color-mix(in oklch, var(--primary), white 40%);
  --primary-dark: color-mix(in oklch, var(--primary), black 30%);
  --primary-muted: color-mix(in oklch, var(--primary), transparent 60%);
  --primary-subtle: color-mix(in oklch, var(--primary), var(--color-surface) 85%);
}
```

Use `color-mix()` for hover/active states, overlays, and surface tints rather than separate hardcoded values.

## Step 2: Semantic Token Layer

Map primitives to purpose-driven names. These are the tokens components actually consume.

```css
:root {
  color-scheme: light dark;

  /* Surfaces */
  --color-surface: light-dark(var(--neutral-0), var(--neutral-900));
  --color-surface-raised: light-dark(var(--neutral-0), var(--neutral-800));
  --color-surface-sunken: light-dark(var(--neutral-50), var(--neutral-1000));
  --color-surface-overlay: light-dark(
    oklch(100% 0 0 / 0.8),
    oklch(15% 0 0 / 0.8)
  );

  /* Text */
  --color-text: light-dark(var(--neutral-900), var(--neutral-100));
  --color-text-muted: light-dark(var(--neutral-600), var(--neutral-400));
  --color-text-subtle: light-dark(var(--neutral-500), var(--neutral-500));

  /* Borders */
  --color-border: light-dark(var(--neutral-200), var(--neutral-700));
  --color-border-strong: light-dark(var(--neutral-400), var(--neutral-500));

  /* Interactive */
  --color-primary: light-dark(var(--primary-500), var(--primary-400));
  --color-primary-hover: light-dark(var(--primary-600), var(--primary-300));
  --color-primary-text: light-dark(var(--neutral-0), var(--neutral-900));

  /* Status */
  --color-success: light-dark(oklch(50% 0.18 150), oklch(70% 0.18 150));
  --color-warning: light-dark(oklch(55% 0.18 85), oklch(75% 0.18 85));
  --color-error: light-dark(oklch(50% 0.22 25), oklch(70% 0.20 25));
  --color-info: light-dark(oklch(50% 0.15 250), oklch(70% 0.15 250));
}
```

### Naming Rules

- Primitives: `--{color}-{shade}` (e.g., `--primary-500`)
- Semantics: `--color-{purpose}` (e.g., `--color-surface`, `--color-text`)
- Component: `--_{property}` with underscore prefix (e.g., `--_bg`, `--_border`)

## Step 3: Light/Dark Mode

### Automatic with light-dark()

The `light-dark()` function reads `color-scheme` and picks the correct value automatically. The `color-scheme` declaration on `:root` is required:

```css
:root {
  color-scheme: light dark;
}
```

This single declaration enables the browser's native light/dark toggle. The user's OS preference drives it via `prefers-color-scheme`.

### Manual Override

Allow users to force a theme with a data attribute:

```css
:root, [data-theme="light"] {
  color-scheme: light;
}

[data-theme="dark"] {
  color-scheme: dark;
}
```

The `light-dark()` values in semantic tokens automatically respond to the `color-scheme` change — no need to redeclare every variable.

### Theme Toggle (minimal JS)

```js
document.documentElement.dataset.theme =
  document.documentElement.dataset.theme === "dark" ? "light" : "dark";
```

Store preference in `localStorage`. On load, set the attribute before first paint to avoid flash.

## Step 4: Component-Level Tokens

Components should use internal custom properties (underscore prefix) mapped to semantic tokens:

```css
.button {
  --_bg: var(--color-primary);
  --_color: var(--color-primary-text);
  --_border: transparent;
  --_radius: var(--radius-m);

  background: var(--_bg);
  color: var(--_color);
  border: 1px solid var(--_border);
  border-radius: var(--_radius);

  &:hover {
    --_bg: var(--color-primary-hover);
  }

  &[data-variant="outline"] {
    --_bg: transparent;
    --_color: var(--color-primary);
    --_border: var(--color-primary);
  }

  &[data-variant="ghost"] {
    --_bg: transparent;
    --_color: var(--color-primary);
  }
}
```

This pattern lets variants, states, and themes override internal tokens without touching property declarations.

## Step 5: @property for Typed and Animated Custom Properties

Register custom properties to enable transitions and type checking:

```css
@property --gradient-angle {
  syntax: "<angle>";
  inherits: false;
  initial-value: 0deg;
}

@property --color-primary-l {
  syntax: "<percentage>";
  inherits: true;
  initial-value: 55%;
}
```

### Use Cases for @property

- **Animate gradients**: transition `--gradient-angle` to spin a gradient
- **Animate colors**: transition individual oklch channels
- **Type safety**: constrain values to `<length>`, `<color>`, `<number>`, etc.
- **Fallback enforcement**: `initial-value` acts as a guaranteed fallback

### Animated Gradient Example

```css
@property --angle {
  syntax: "<angle>";
  inherits: false;
  initial-value: 0deg;
}

.gradient-border {
  background: conic-gradient(from var(--angle), var(--primary-400), var(--primary-600), var(--primary-400));
  transition: --angle 500ms ease;

  &:hover {
    --angle: 180deg;
  }
}
```

## Step 6: Contrast and Forced-Colors Support

### prefers-contrast: more

Increase visual distinction for users who request it:

```css
@media (prefers-contrast: more) {
  :root {
    --color-text: light-dark(oklch(5% 0 0), oklch(98% 0 0));
    --color-border: light-dark(oklch(30% 0 0), oklch(80% 0 0));
    --color-text-muted: light-dark(oklch(30% 0 0), oklch(80% 0 0));
  }
}
```

### forced-colors: active

Support Windows High Contrast mode. System colors replace your custom colors:

```css
@media (forced-colors: active) {
  .button {
    border: 1px solid ButtonText;
  }

  .card {
    border: 1px solid CanvasText;
  }

  .badge {
    outline: 1px solid;
  }
}
```

Key system color keywords: `Canvas`, `CanvasText`, `LinkText`, `ButtonFace`, `ButtonText`, `Highlight`, `HighlightText`, `GrayText`.

Rules in forced-colors mode:
- Custom colors are overridden — don't fight it
- Borders and outlines become the primary visual indicators
- Ensure interactive elements remain distinguishable
- `background-image` is removed — don't rely on it for meaning

## Step 7: Multiple Themes (Beyond Light/Dark)

For brand themes, seasonal themes, or user-customizable themes, use data attributes with full token overrides:

```css
[data-theme="ocean"] {
  color-scheme: dark;
  --primary-500: oklch(60% 0.20 230);
  --color-surface: oklch(18% 0.02 230);
  --color-text: oklch(90% 0.01 230);
}

[data-theme="forest"] {
  color-scheme: dark;
  --primary-500: oklch(55% 0.18 150);
  --color-surface: oklch(15% 0.02 150);
  --color-text: oklch(90% 0.01 150);
}
```

Override only the primitives — semantic tokens that reference primitives via `var()` update automatically.

### User-Customizable Themes

Expose a small set of properties for user control:

```css
:root {
  --user-hue: 250;
  --primary-500: oklch(55% 0.25 var(--user-hue));
}
```

Set `--user-hue` from JavaScript based on user preference.

## Output Format

When building a theme system, deliver:

1. **Token file** — all primitive and semantic tokens in one file (e.g., `tokens.css`)
2. **Theme variants** — separate `@layer` blocks or data-attribute overrides
3. **Component examples** — show 2-3 components using the token system
4. **Accessibility layer** — `prefers-contrast` and `forced-colors` overrides

## Rules

- Always declare `color-scheme: light dark` on `:root` when using `light-dark()`
- Never hardcode colors in components — always reference tokens
- Use `oklch()` for all color definitions, never hex/rgb/hsl
- Use `color-mix(in oklch, ...)` for derived colors, not manually computed values
- Test that contrast ratios meet WCAG: 4.5:1 for normal text, 3:1 for large
- Component tokens use underscore prefix (`--_bg`) to signal internal scope
- Semantic tokens use `--color-` prefix for discoverability

Related Skills

open-u-dashboard

7
from Lionad-Morotar/local-tools

open understand dashboard for user

sync-template-skill

7
from Lionad-Morotar/local-tools

这是一个技能文件的模板,展示了技能的基本结构和内容组织方式。

talk-humanize

7
from Lionad-Morotar/local-tools

Be direct and informative. No filler, no fluff, but give enough to be useful.

search-web

7
from Lionad-Morotar/local-tools

使用 Evaluator-optimizer 模式进行系统性多轮网络搜索,采用结构化 Ask 流程在搜索前澄清研究目标。基于 YC Office Hours 的提问方法论,确保搜索方向清晰、结果可验证。当用户需要深入调查复杂主题、验证假设或全面收集信息时使用。

save-to-eagle

7
from Lionad-Morotar/local-tools

归档网络内容到 Eagle 素材库。支持:(1) Behance/Pixiv 图片归档,(2) 网页视频录制(页面动画、滚动录制)。使用方式:'归档 [URL]' 归档图片;'录制网页视频 [URL]' 录制页面动画;'滚动录制 [URL]' 自动滚动截图。支持评分如 '归档 [URL], 3/5'。

save-ob-chaos

7
from Lionad-Morotar/local-tools

将对话内容快速存档到 Obsidian Chaos 文件夹。触发词:"存档到 Obsidian"、"保存到 Chaos"、"ob 存档"、"记下这个"、"保存这段内容"、"存到 chaos"。

save-ob-chaos-mermaid

7
from Lionad-Morotar/local-tools

将 Mermaid 图表保存到 Obsidian Chaos 文件夹。触发词:"保存 mermaid 到 chaos"、"mermaid 存档"。

save-ob-chaos-excalidraw

7
from Lionad-Morotar/local-tools

绘制 Excalidraw 图表并存档到 Obsidian Chaos 文件夹。触发词:"画个图存到 Obsidian"、"excalidraw 存档"、"画个流程图保存"、"画图存到 chaos"、"创建图表并存档"、"画架构图到 ob"。

release-project

7
from Lionad-Morotar/local-tools

项目版本发布流程指导,帮助用户完成版本规划、Changelog 管理、版本号升级、Git 标签创建和 npm 首次发布准备。Use when: (1) 用户需要发布新版本 (2) 需要创建版本发布流程 (3) 需要管理版本号和 Changelog (4) 需要自动化版本发布 (5) 需要检查 release 分支同步 (6) 首次 npm 发布准备

recognize-codebase-branch-flow

7
from Lionad-Morotar/local-tools

识别并记忆项目 git 分支模型

rebase-commits

7
from Lionad-Morotar/local-tools

将零散的 commits 整合为清晰的逻辑提交,使 Git 历史更易读。 Use when: (1) 用户说 "rebase commits"、"整理提交历史"、"让历史更干净" (2) 用户想将多个相关 commits 合并为逻辑单元 (3) 完成一个功能后需要清理 commit 历史 (4) 提交历史混乱,需要重新组织

read-codebase

7
from Lionad-Morotar/local-tools

阅读棕地项目代码库,智能分析代码结构,递归补充其调用链上所有函数的注释。