A color system that holds up in light, dark and every market

jubayer-colors helps Claude build palettes as ramps, name tokens by role, measure real contrast and keep color meaning honest, including in right to left layouts and other markets.

  • Free, MIT License
  • 10 files, 24 KB zip

Updated 28 October 2026

When Claude uses it

Claude reads this description from the skill's SKILL.md and loads the skill when your request matches it.

Builds and reviews color systems, including palettes and ramps, primitive and semantic tokens, format conversion, measured WCAG 2 and APCA contrast, gradients, dark mode and P3 fallbacks. Also covers color in bilingual Arabic/English and Bangla/English products, where meaning varies by culture (price up/down colors per market, never hue alone) and directional gradients, shadows and chart ramps must follow right-to-left layouts. Use when creating or auditing a palette or color tokens, choosing or converting colors, checking contrast, fixing dark mode, or adapting color for RTL or for another market.

What it covers

  • Ramps, not single colors

    A system is ramps where every step has a job, with the hue held steady across the ramp.

  • Primitives by hue, semantics by role

    Primitive tokens named by hue, semantic tokens by role, each used only in its role.

  • One color, one meaning

    One filled action per view, and the danger color never on a harmless action.

  • Measure the rendered pair

    Contrast checked on the colors as they render, then reported, in light and dark.

  • Gradients and formats

    Pick a gradient's interpolation space, and convert between formats correctly.

  • Never hue alone

    Meaning always has a second cue, and color meanings are not assumed to be universal.

  • Markets and RTL

    Price colors follow the market or a user setting; directional gradients and shadows get an RTL override.

What this version adds

A factual reference on color across cultures and in RTL: no invented color meanings, the documented red-up and green-down price convention, role-named tokens a market can remap, RTL gradients and chart ramps, and contrast for Arabic and Bangla text.

Try it with a prompt like this

I'm adding Arabic RTL support and a stock ticker for users in Asia. Can you check my color tokens, gradients and contrast?

Or run it by name in Claude Code: type /jubayer-colors and what you want checked.

View SKILL.md

The skill's main file, as it is in the zip. The reference files it links to are listed below it.

jubayer-colors/SKILL.md · Show the full file
---
name: jubayer-colors
description: Builds and reviews color systems, including palettes and ramps, primitive and semantic tokens, format conversion, measured WCAG 2 and APCA contrast, gradients, dark mode and P3 fallbacks. Also covers color in bilingual Arabic/English and Bangla/English products, where meaning varies by culture (price up/down colors per market, never hue alone) and directional gradients, shadows and chart ramps must follow right-to-left layouts. Use when creating or auditing a palette or color tokens, choosing or converting colors, checking contrast, fixing dark mode, or adapting color for RTL or for another market.
---

# Colors

Treat color as a system: a few ramps, each step tied to a role, every token named for its job and every pair checked against the background it really sits on. When color goes wrong it is rarely one bad value. It is a value chosen without the ramp, a token reused because it happened to match, or a pair no one measured.

Color questions usually have an exact answer, so compute it. Do not estimate a value you can calculate, and do not report a contrast figure you have not measured.

Contrast requirements belong to `jubayer-accessibility`. Surfaces, shadows and icon color belong to `jubayer-ui`.

## Match the project's color system

Reuse the project's tokens and notation. A second representation added to fix one value makes the palette harder to reason about. A consistent hex system beats hex with `oklch()` scattered through it.

For a new system, `oklch()` is the best default, because its numbers behave the way the ramp rules below describe. Everywhere else, a color library produces the same ramp in the project's own notation ([color-formats.md](color-formats.md)).

## A system is ramps, not colors

One neutral ramp, one accent ramp and only the status ramps the product actually renders. A `warning` ramp nothing imports is maintenance for zero pixels. A second accent hue earns its place only when two things must be distinguishable at a glance.

## Every step has a job

A ramp is not a gradient to pick from by eye. Each step exists because a role needs it: page background, component hover, border, solid fill, body text. Do not generate a step no role consumes. Both the Tailwind `50` to `950` and Radix `1` to `12` conventions map to those roles ([palette-structure.md](palette-structure.md)).

## Name primitives by hue, semantics by role

Primitives name a value (`--blue-500`) and are never applied in a component. Semantic tokens name a job (`--color-text-secondary`), point at a primitive and are the only tier components reference.

That seam is what makes theming possible. Without it, dark mode means auditing every usage to work out which meant "the accent" and which just wanted blue ([token-naming.md](token-naming.md)).

## Use a token only in its role

Never borrow a token because its value is right today. A separator used as a text color works until borders get lighter, and then the text goes with them. If a role has no token, add the token.

## Hold the hue across the ramp

Four properties define a well-formed ramp:

- Steps step evenly in *perceived* lightness, not in whatever the format calls lightness.
- Hue stays constant end to end.
- Vividness peaks mid-ramp and falls off at both ends.
- Steps sit denser at the light end than at the dark end.

Both ends stop short of pure black and white, which cannot carry hue at all. Use a color library rather than eyeballing it ([palette-generation.md](palette-generation.md)).

## One color, one meaning

Use a color for one purpose across the whole interface, treating anything within `15°` of hue as the same color. If the accent means interactive, that hue on static text tells users to click something that is not clickable, and an interactive element rendered neutral misleads just as badly. Color is never the only carrier of meaning, which `jubayer-accessibility` owns.

## Fill exactly one action per view

When filled color encodes primary emphasis, one primary action gets it and peers stay neutral. Put the color on the background, not the label. A filled button reads as primary across the room; accent-colored text on a neutral button reads as a link.

Several colored backgrounds are fine when they encode distinct states or categories rather than competing as peers.

## Measure the rendered pair, then report

Measure a foreground against the background it actually renders on, not the page background. When a pair fails, report the pair, its measured value and the threshold it misses, then leave the colors alone. They are a design decision. Change them only when asked, and remeasure after ([contrast.md](contrast.md)).

## Pick a gradient's interpolation space

The space is a look, not a correctness setting.

- **`in oklab`** is the best default: even brightness, no hue surprises.
- **`in oklch`** travels around the hue wheel rather than through the middle, staying vivid and sweeping every hue between the stops. Reach for it when a two-hue gradient goes gray in the middle.
- **The sRGB default** darkens and mutes the midpoint. It is what most interfaces already have, because it is what you get without asking.

See [color-usage.md](color-usage.md).

## Color across cultures and in RTL

Hue meaning varies by culture and context, so it never carries meaning alone (WCAG 1.4.1 Use of Color): pair it with an icon, a sign or a label. Price changes are the documented case: in mainland China and some other East Asian markets red means up and green means down, so price colors follow the market or a user setting, never `lang`, and always come with an arrow and a sign. Name tokens by role (`success`, `danger`, `change-up`) so a market remaps one block. In RTL, gradients have no logical keywords and shadow x offsets are physical, so override directional ones under `:dir(rtl)`; keep chart legends and ramps running the same way as their axis. Arabic and Bangla text uses the same contrast thresholds, but keep it well clear of them ([color-across-cultures-and-rtl.md](color-across-cultures-and-rtl.md)).

## Before you finish

| Mistake | Fix |
| --- | --- |
| A raw value where the project has a token | Reuse or add the role token, in the project's notation |
| An isolated `oklch()` value dropped into a hex codebase | Keep the established notation unless a migration is in scope |
| A primitive like `--blue-500` used directly in a component | Point a semantic token at it |
| Token named for its appearance (`--color-blue-button`) or first use (`--color-sidebar-gray`) | Name it for its role: `--color-accent-solid`, `--color-bg-surface` |
| `--color-primary` meaning the brand and `--color-text-primary` meaning body text | Reserve `accent` for the brand; let `primary` mean "most prominent of its group" |
| Semantic token used outside its role (separator as text) | Add a token for the missing role; never borrow by value |
| Ramp built by varying HSL lightness | Rebuild against perceived lightness with a constant hue |
| Ramp spaced evenly across the full range | Tighten the light end until `50` and `100` read as two surfaces |
| Same saturation number reused across hues | Match the proportion of each hue's own maximum, not the raw value |
| Status hue that collides with the accent hue | Move it until destructive and primary read apart side by side |
| Dark mode made by mechanically reversing the light palette | Reverse as a starting point, then reduce vividness, widen the dark end and recheck every pair |
| `prefers-color-scheme` setting some tokens and a `.dark` class setting others | Pick one switching mechanism and use it throughout |
| Contrast fixed by changing hue | Change lightness, the channel contrast responds to |
| P3 color with no sRGB fallback | Declare the sRGB value first, then override inside `@media (color-gamut: p3)` |
| Red and green hard-coded for price up and down | `--color-change-up` / `--color-change-down` set per market or user setting, with an arrow and a sign |
| Token named `--color-green` or `--color-positive` where a market may remap it | Name the role: `--color-success`, `--color-change-up` |
| `linear-gradient(to right, ...)` fade or edge shadow that stays put in RTL | Override the direction or negate the x offset under `:dir(rtl)` |
| Chart legend running the opposite way to its mirrored axis | Mirror the legend and ramp with the axis, or mirror neither |
| Arabic or Bangla text in a light weight on a pair that barely passes | Regular or medium weight, and a pair well clear of the threshold |

## Reporting

**Severity.** `HIGH` makes content unreadable or assigns a misleading semantic color. `MEDIUM` is a noticeable theme, token, or gamut failure. `LOW` is isolated polish.

**Verification.** Without a browser: token values, the gamut of every declared color, both theme blocks present and contrast computed from the declared token pair. With one: the background actually rendered behind the text, including opacity and any image beneath it, measured in both light and dark. In the RTL locale, check every directional gradient, edge shadow and chart legend. A failing pair is reported, not repainted. Report every check you could not run as `Not verified`.

**Format.** Group findings under the principle each violates, ordered by severity, one row per root cause listing every location it appears in:

| Severity | Location | Before | After | Why |
| --- | --- | --- | --- | --- |

`Location` is `path/to/file:line`. `Why` names the principle and the user impact.

End with `Block` when any `HIGH` remains, `Approve` otherwise, leaving the rest in the table as work to do. Never `Approve` coverage you did not inspect. With nothing to report, state "No actionable color findings" and report verification.

## Origin

Based on Jakub Krehel's `better-colors` skill (MIT License), renamed and edited by Maniruzzaman Jubayer, with added right-to-left and bilingual rules. See LICENSE.

Files in the zip

  • jubayer-colors/LICENSE1 KB
  • jubayer-colors/SKILL.md10 KB
  • jubayer-colors/agents/openai.yaml1 KB
  • jubayer-colors/color-across-cultures-and-rtl.md8 KB
  • jubayer-colors/color-formats.md4 KB
  • jubayer-colors/color-usage.md6 KB
  • jubayer-colors/contrast.md5 KB
  • jubayer-colors/palette-generation.md6 KB
  • jubayer-colors/palette-structure.md5 KB
  • jubayer-colors/token-naming.md5 KB

Based on Jakub Krehel's better-* skills, MIT License. The LICENSE in the zip keeps his copyright and mine.

Install jubayer-colors in 3 steps

Pick where you use Claude. The same zip works in both places.

In the Claude app or on Claude.ai

  1. Step 1:

    Open Settings › Capabilities. Under Code execution and file creation, switch on Run code in the cloud. Skills need it.

    Claude settings at Capabilities, showing Code execution and file creation with the Run code in the cloud switch turned on, noted as required for skills
    Settings › Capabilities: Run code in the cloud, on
  2. Step 2:

    Open Settings › Customize › Skills, choose + Add, then Upload skill.

    The Skills section of Customize in Claude with the + Add menu open, showing Create a skill, Create with Claude and Upload skill, with Upload skill outlined
    Customize › Skills › + Add › Upload skill
  3. Step 3:

    Drop the skill's zip, as it downloads from here, on Upload a skill and choose Upload. Claude scans it, then lists it under Skills, where you switch it on or off.

    The Upload a skill page in Claude with its drop zone outlined, which takes a .zip or .skill file that includes a SKILL.md and runs a security scan on upload
    Upload a skill: drop the .zip, then Upload

In Claude Code

  1. Step 1:

    Unzip the skill into your skills folder: ~/.claude/skills/ for every project, or .claude/skills/ in one project.

    A terminal running mkdir -p ~/.claude/skills and unzip jubayer-ui.zip -d ~/.claude/skills
    Unzip into ~/.claude/skills
  2. Step 2:

    Check the folder is there. Each skill is a folder with a SKILL.md inside.

    A terminal listing ~/.claude/skills with the jubayer skill folders and the files inside one of them
    Each skill is its own folder
  3. Step 3:

    Ask Claude for the job in plain words, or type the skill's name as a command, such as /jubayer-ui.

    A terminal starting Claude Code in a project folder, with /jubayer-ui polish the settings page typed as the request
    Run it by name or just ask

Want a color system built with your product?

I design color systems as tokens that ship in the code, checked for contrast in light and dark.

Questions people ask

  • Yes. It builds full ramps from a brand color, names the tokens and checks the contrast of the pairs you actually use.
  • No. Color associations vary by culture and context. The skill names tokens by role, such as success and danger, and always pairs color with an icon, sign or label.
  • CSS gradients have no logical direction keywords, so a gradient to the right stays to the right when the page flips. The skill adds the override under :dir(rtl).

Want this level of detail in your product?

I design and build products with these rules from the first flow, in English, Arabic or both. Start with a free 30 minute call, then get one written scope and one fixed quote.

Book a free intro call