@huekit/tailwind
v1.0.0
Published
Turn brand colors into a Tailwind v4 @theme block. OKLCH scales, gamut-fitted, with readable foregrounds. Part of huekit, the OKLCH toolkit for designers.
Maintainers
Readme
@huekit/tailwind
Turn brand colors into a Tailwind v4
@themeblock. Gamut-fitted OKLCH scales with readable foregrounds. Part of huekit, the OKLCH toolkit for designers.
import { theme } from '@huekit/tailwind'
theme({ brand: '#3b82f6' })
// @theme {
// --color-brand-50: oklch(0.971 0.013 259.815);
// --color-brand-50-fg: #000000;
// --color-brand-500: oklch(0.602 0.188 259.815);
// --color-brand-500-fg: #000000;
// ...
// }Paste that into your Tailwind entry file and you have bg-brand-500, text-brand-700, border-brand-200 and the rest of the scale, generated for you.
What this does
Tailwind v4 moved theming into CSS. You declare --color-* custom properties inside @theme and Tailwind generates the matching utilities. The remaining work is deciding what those colors should be, which is the part that is easy to get wrong by hand.
@huekit/tailwind takes your brand hex and produces the full block: an eleven stop scale per color, chroma fitted so every stop is a color sRGB can actually render, and a readable foreground for each stop with the contrast to back it up.
It is a formatter over @huekit/tokens, so the color work is shared with the rest of the suite rather than reimplemented here.
Install
npm install @huekit/tailwindUsage
Generate the block and write it into your CSS entry file:
import { writeFileSync } from 'node:fs'
import { theme } from '@huekit/tailwind'
writeFileSync(
'src/theme.css',
theme({ brand: '#3b82f6', danger: '#ef4444' }, { includeImport: true })
)Then use the utilities as normal:
<button class="bg-brand-500 text-brand-500-fg hover:bg-brand-600">
Save
</button>API
theme(input, options?)
Input is a hex string, an OKLCH object, or an object of named colors:
theme('#3b82f6') // → --color-color-50 … 950
theme({ l: 0.6, c: 0.19, h: 260 }) // → same, from OKLCH
theme({ brand: '#3b82f6', danger: '#ef4444' }) // → --color-brand-* and --color-danger-*Returns the @theme block as a string, or the underlying TokenSet with { raw: true }.
| Option | Type | Default | Description |
|--------|------|---------|-------------|
| raw | boolean | false | Return the TokenSet instead of CSS |
| precision | number | 4 | Decimal places in oklch() values |
| stops | Record<string, number> | 11-stop ramp | Custom stop → lightness map |
| chromaFit | 'fit' \| 'keep' | 'fit' | Fit chroma to the sRGB gamut per stop, or keep the base chroma |
| foregrounds | boolean | true | Emit a -fg variable per stop |
| includeImport | boolean | false | Prepend @import "tailwindcss"; |
toTailwind(set, options?)
Formats an existing TokenSet as a @theme block. Useful when you want to filter or adjust the model first.
const set = theme({ brand: '#3b82f6' }, { raw: true })
delete set.colors.brand.stops['950']
toTailwind(set)Hand-built or edited stops must keep the canonical oklch(<l> <c> <h>) value format and a #000000 or #ffffff foreground. Invalid values, unsafe identifiers, empty models, and duplicate generated variables throw instead of emitting malformed CSS.
Staying inside the gamut
Holding chroma constant across a lightness ramp asks for colors sRGB cannot render. At the light end of a saturated blue scale the requested chroma can be more than ten times what the gamut allows.
By default chroma is fitted per stop to the most saturated value sRGB can show at that lightness, so scales stay vivid through the midtones and taper at the ends. Set chromaFit: 'keep' to hold the base chroma instead, for example when targeting Display P3.
Readable foregrounds
Each stop ships a -fg variable carrying whichever of black or white has better contrast on it. On a typical scale the foreground flips somewhere in the middle, and nothing in a generated scale drops below WCAG AA.
.button {
background: var(--color-brand-500);
color: var(--color-brand-500-fg);
}Disable them with foregrounds: false if you handle text color yourself.
Notes
This package is ESM only. Its one dependency is @huekit/tokens.
Color names, stop names, color values, foregrounds and generated variable names are validated before they reach the output, so malformed or injected CSS throws rather than reaching the returned block.
Requires Tailwind v4. The @theme directive does not exist in v3.
The huekit suite
| Package | What it does |
|---------|--------------|
| @huekit/hex | hex → oklch() conversion |
| @huekit/scale | perceptual lightness scale from one color |
| @huekit/contrast | WCAG & APCA contrast + readability helpers |
| @huekit/mix | blend colors, no muddy midpoint |
| @huekit/tokens | the token model: scales, foregrounds, CSS variables |
| @huekit/tailwind ← you are here | Tailwind v4 @theme formatter |
License
MIT
