/setting-up-charcoal-css-variables
Use before writing or editing any code that imports @charcoal-ui/react, and whenever a project's charcoal setup is unverified, incomplete or being finished. Charcoal components render completely unstyled unless BOTH @charcoal-ui/theme/css/v2/light.css is imported AND the
Claude Code
$ npx -y skills add pixiv/charcoal --skill setting-up-charcoal-css-variables --agent claude-code
Codex
$ npx skills add pixiv/charcoal --skill setting-up-charcoal-css-variables -a codex -y
Cursor
$ npx skills add pixiv/charcoal --skill setting-up-charcoal-css-variables -a cursor -y
Gemini CLI
$ npx skills add pixiv/charcoal --skill setting-up-charcoal-css-variables -a gemini-cli -y
GitHub Copilot
$ npx skills add pixiv/charcoal --skill setting-up-charcoal-css-variables -a github-copilot -y
OpenCode
$ npx skills add pixiv/charcoal --skill setting-up-charcoal-css-variables -a opencode -y
Windsurf
$ npx skills add pixiv/charcoal --skill setting-up-charcoal-css-variables -a windsurf -y
SKILL.md
setting-up-charcoal-css-variables.SKILL.mdname: setting-up-charcoal-css-variables
description: Use before writing or editing any code that imports @charcoal-ui/react, and whenever a project's charcoal setup is unverified, incomplete or being finished. Charcoal components render completely unstyled unless BOTH @charcoal-ui/theme/css/v2/light.css is imported AND the .ch-token-v2 class is on <html>; importing dist/index.css alone, or adding the stylesheet without the class, silently produces correct geometry with no colors at all.
Setting up charcoal with CSS variables
The lowest-dependency way to use charcoal: load two stylesheets, add one class, then write `var(--charcoal-*)` anywhere.
Install
npm install @charcoal-ui/react@^6 @charcoal-ui/theme@^6 react-aria react-stately
Pin the major. Design Token 2.0 and the CSS layout below are v6; v5 and earlier differ. Keep every `@charcoal-ui/*` package on the same major.
Peer dependencies of `@charcoal-ui/react`: `react >=17.0.0`, `react-aria >=3.48.0`, `react-stately >=3.46.0`. The two react-aria packages are **not** installed for you. React 18 and 19 both satisfy the range.
Skip `@charcoal-ui/react` if you only want tokens — `@charcoal-ui/theme` alone is enough.
Load the CSS
// Load once at the app root.
import '@charcoal-ui/react/dist/index.css' // component implementation CSS
import '@charcoal-ui/theme/css/v2/light.css' // Design Token 2.0 — light
import '@charcoal-ui/theme/css/v2/dark.css' // Design Token 2.0 — dark
`dist/index.css` only _consumes_ `var(--charcoal-color-*)`. It never defines them.
The failure this causes is lopsided in a way that misleads people: **colors go through `var()`, but layout does not.** Padding, radius, font-size, and line-height are written as literal values, so they keep working. Load `dist/index.css` alone and you get a correctly shaped, correctly sized button with a transparent background and default black text. Nothing errors — an undefined custom property just makes that one declaration invalid, and the property falls back to its initial value.
If a charcoal component looks _structurally_ right but _colorless_, this is why.
Prefer `@charcoal-ui/react/dist/layered.css` over `dist/index.css` when you need to override charcoal styles from your own CSS. Its contents are wrapped in `@layer charcoal`, so any unlayered rule of yours wins without a specificity fight. It requires iOS 15.4+.
Opt in to Design Token 2.0 — required
Token 2.0 only applies inside `.ch-token-v2`.
<html class="ch-token-v2" lang="ja"></html>
The shipped selectors are:
/* light.css */
:root.ch-token-v2[data-theme='light'],
:root.ch-token-v2:not([data-theme]),
:root[data-theme='light'] .ch-token-v2,
:root:not([data-theme]) .ch-token-v2 {
--charcoal-color-…: …;
}
/* dark.css */
:root[data-theme='dark'] {
--charcoal-color-…: …;
}Two consequences:
- Without `.ch-token-v2`, **no light-theme rule matches** and every token is undefined.
- Dark is scoped to `:root` only, so `data-theme="dark"` applies page-wide regardless of `.ch-token-v2`.
Put the class on `<html>`, or on an ancestor of the components you want themed. Variables inherit, so siblings are unaffected.
Use the tokens
.card {
background-color: var(--charcoal-color-background-default);
color: var(--charcoal-color-text-default);
border: 1px solid var(--charcoal-color-border-secondary);
border-radius: var(--charcoal-radius-s);
padding: var(--charcoal-space-40);
}Names follow `--charcoal-<category>-<semantic>-<state>`. Common ones: `color-text-default` / `-secondary-default` / `-tertiary-default`, `color-background-default` / `-secondary` / `-tertiary`, `color-border-default` / `-secondary`, `color-container-primary-default`, `radius-xs|s|m|l`, `space-*`. Read `node_modules/@charcoal-ui/theme/dist/css/v2/light.css` for the full list.
Components need no provider
import { Button } from '@charcoal-ui/react'
export const Save = () => <Button variant="Primary">保存</Button>`Button` imports its own CSS and uses no context. Wrap in `CharcoalProvider` **only** when you use overlays (`Modal`, `DropdownSelector`) — it supplies react-aria's `SSRProvider` and `OverlayProvider`.
Theme switching
document.documentElement.dataset.theme = 'dark' // or 'light'
For SSR, run this before paint to avoid a flash:
import { makeSetThemeScriptCode } from '@charcoal-ui/react'
;<script dangerouslySetInnerHTML={{ __html: makeSetThemeScriptCode() }} />Use `makeSetThemeScriptCode()`, not `<SetThemeScript />`. The component's props type requires both `localStorageKey` and `rootAttribute`, so `<SetThemeScript />` is a type error; `makeSetThemeScriptCode()` accepts zero arguments.
System theme does not follow the OS
`useThemeSetter()` **removes** `data-theme` when the user is on "system", by design — it expects CSS to handle `prefers-color-scheme`. Design Token 2.0's CSS has no `prefers-color-scheme` block, so with the attribute removed `:root:not([data-theme]) .ch-token-v2` matches and the page is **stuck on light** even on a dark OS.
Either always write an explicit `data-theme`, or add the missing bridge yourself:
@media (prefers-color-scheme: dark) {
:root:not([data-theme='light']).ch-token-v2,
:root:not([data-theme='light']) .ch-token-v2 {
/* copy the declarations from @charcoal-ui/theme/css/v2/dark.css */
}
}Mirror light.css's selector shape. Targeting bare `:root` fails when `.ch-token-v2` sits on a descendant: that descendant's own light values beat inherited dark values from an ancestor.
Common mistakes
| Mistake | Fix | | ---------------------------------- | ------------------------------------------------ | | Only `dist/index.css` loaded | Add `@charcoal-ui/theme/css/v2/{light,dark}.css` | | `.ch-token-v2` missing | Add it to `<html>` or an ancestor | | `<
Read more
name: setting-up-charcoal-css-variables description: Use before writing or editing any code that imports @charcoal-ui/react, and whenever a project's charcoal setup is unverified, incomplete or being finished. Charcoal components render completely unstyled unless BOTH @charcoal-ui/theme/css/v2/light.css is imported AND the .ch-token-v2 class is on <html>; importing dist/index.css alone, or adding the stylesheet without the class, silently produces correct geometry with no colors at all.
Setting up charcoal with CSS variables
The lowest-dependency way to use charcoal: load two stylesheets, add one class, then write `var(--charcoal-*)` anywhere.
Install
npm install @charcoal-ui/react@^6 @charcoal-ui/theme@^6 react-aria react-stately
Pin the major. Design Token 2.0 and the CSS layout below are v6; v5 and earlier differ. Keep every `@charcoal-ui/*` package on the same major.
Peer dependencies of `@charcoal-ui/react`: `react >=17.0.0`, `react-aria >=3.48.0`, `react-stately >=3.46.0`. The two react-aria packages are **not** installed for you. React 18 and 19 both satisfy the range.
Skip `@charcoal-ui/react` if you only want tokens — `@charcoal-ui/theme` alone is enough.
Load the CSS
// Load once at the app root. import '@charcoal-ui/react/dist/index.css' // component implementation CSS import '@charcoal-ui/theme/css/v2/light.css' // Design Token 2.0 — light import '@charcoal-ui/theme/css/v2/dark.css' // Design Token 2.0 — dark
`dist/index.css` only _consumes_ `var(--charcoal-color-*)`. It never defines them.
The failure this causes is lopsided in a way that misleads people: **colors go through `var()`, but layout does not.** Padding, radius, font-size, and line-height are written as literal values, so they keep working. Load `dist/index.css` alone and you get a correctly shaped, correctly sized button with a transparent background and default black text. Nothing errors — an undefined custom property just makes that one declaration invalid, and the property falls back to its initial value.
If a charcoal component looks _structurally_ right but _colorless_, this is why.
Prefer `@charcoal-ui/react/dist/layered.css` over `dist/index.css` when you need to override charcoal styles from your own CSS. Its contents are wrapped in `@layer charcoal`, so any unlayered rule of yours wins without a specificity fight. It requires iOS 15.4+.
Opt in to Design Token 2.0 — required
Token 2.0 only applies inside `.ch-token-v2`.
<html class="ch-token-v2" lang="ja"></html>
The shipped selectors are:
/* light.css */
:root.ch-token-v2[data-theme='light'],
:root.ch-token-v2:not([data-theme]),
:root[data-theme='light'] .ch-token-v2,
:root:not([data-theme]) .ch-token-v2 {
--charcoal-color-…: …;
}
/* dark.css */
:root[data-theme='dark'] {
--charcoal-color-…: …;
}Two consequences:
- Without `.ch-token-v2`, **no light-theme rule matches** and every token is undefined.
- Dark is scoped to `:root` only, so `data-theme="dark"` applies page-wide regardless of `.ch-token-v2`.
Put the class on `<html>`, or on an ancestor of the components you want themed. Variables inherit, so siblings are unaffected.
Use the tokens
.card {
background-color: var(--charcoal-color-background-default);
color: var(--charcoal-color-text-default);
border: 1px solid var(--charcoal-color-border-secondary);
border-radius: var(--charcoal-radius-s);
padding: var(--charcoal-space-40);
}Names follow `--charcoal-<category>-<semantic>-<state>`. Common ones: `color-text-default` / `-secondary-default` / `-tertiary-default`, `color-background-default` / `-secondary` / `-tertiary`, `color-border-default` / `-secondary`, `color-container-primary-default`, `radius-xs|s|m|l`, `space-*`. Read `node_modules/@charcoal-ui/theme/dist/css/v2/light.css` for the full list.
Components need no provider
import { Button } from '@charcoal-ui/react'
export const Save = () => <Button variant="Primary">保存</Button>`Button` imports its own CSS and uses no context. Wrap in `CharcoalProvider` **only** when you use overlays (`Modal`, `DropdownSelector`) — it supplies react-aria's `SSRProvider` and `OverlayProvider`.
Theme switching
document.documentElement.dataset.theme = 'dark' // or 'light'
For SSR, run this before paint to avoid a flash:
import { makeSetThemeScriptCode } from '@charcoal-ui/react'
;<script dangerouslySetInnerHTML={{ __html: makeSetThemeScriptCode() }} />Use `makeSetThemeScriptCode()`, not `<SetThemeScript />`. The component's props type requires both `localStorageKey` and `rootAttribute`, so `<SetThemeScript />` is a type error; `makeSetThemeScriptCode()` accepts zero arguments.
System theme does not follow the OS
`useThemeSetter()` **removes** `data-theme` when the user is on "system", by design — it expects CSS to handle `prefers-color-scheme`. Design Token 2.0's CSS has no `prefers-color-scheme` block, so with the attribute removed `:root:not([data-theme]) .ch-token-v2` matches and the page is **stuck on light** even on a dark OS.
Either always write an explicit `data-theme`, or add the missing bridge yourself:
@media (prefers-color-scheme: dark) {
:root:not([data-theme='light']).ch-token-v2,
:root:not([data-theme='light']) .ch-token-v2 {
/* copy the declarations from @charcoal-ui/theme/css/v2/dark.css */
}
}Mirror light.css's selector shape. Targeting bare `:root` fails when `.ch-token-v2` sits on a descendant: that descendant's own light values beat inherited dark values from an ancestor.
Common mistakes
| Mistake | Fix | | ---------------------------------- | ------------------------------------------------ | | Only `dist/index.css` loaded | Add `@charcoal-ui/theme/css/v2/{light,dark}.css` | | `.ch-token-v2` missing | Add it to `<html>` or an ancestor | | `<
Other skills on charcoal.
More skills from other plugins.
banner-design
Design banners for social media, ads, website heroes, creative assets, and print. Multiple art direction options with optional…
impeccable
Use to design, redesign, critique, audit, polish or animate a frontend: websites, landing pages, dashboards, app UI, components,…
8-bit-orbit-video-temp…
Hyperframes-based video template for retro pixel deck motion design. Use when users want a high-fidelity, multi-scene…
diagram-design
Create branded architecture, architecture delta, IT current-state, flowchart, sequence, state machine, ER/data model, timeline,…
Questions about setting-up-charcoal-css-variables.
- Which coding agents can use setting-up-charcoal-css-variables?
- setting-up-charcoal-css-variables installs in Claude Code, Codex, Cursor, Gemini CLI, GitHub Copilot, OpenCode and Windsurf with the skills CLI, using each agent's flag shown on this page. Flowy's auto-invocation, which fires a skill when your prompt matches, runs in Claude Code only.

