@bastianplsfix/colors
v0.1.0
Published
A Tailwind CSS v4 color system: light/dark surfaces, text, borders, status and semantic tokens, with color-mix() blends and display-p3 wide-gamut enhancement.
Maintainers
Readme
@bastianplsfix/colors
A Tailwind CSS v4 color system: light and dark surfaces, foreground ramps,
borders, buttons, shadows, status hues, and semantic alert/danger tokens —
with color-mix() blends and display-p3 wide-gamut colors layered on where
the browser supports them.
CSS only. No JavaScript, no build step.
Install
npm install @bastianplsfix/colorsSetup
Import after Tailwind:
@import "tailwindcss";
@import "@bastianplsfix/colors/theme.css";That's the whole setup. Every token is a Tailwind theme variable, so it works both as a utility and as a raw custom property:
<div class="bg-surface-2 text-fg-1 border-border">…</div>.card {
background: var(--color-surface-2);
box-shadow: var(--shadow-2);
}Dark mode
Light is the default. Dark activates on a .dark class on any ancestor —
usually <html>:
document.documentElement.classList.toggle("dark", isDark);The package declares the matching Tailwind variant for you:
@custom-variant dark (&:where(.dark, .dark *));so dark: utilities and the palette flip from the same switch. Do not also
declare @custom-variant dark in your app CSS — and if you were using
Tailwind's default prefers-color-scheme dark variant, importing this package
switches you to the class strategy.
Because .dark is a class and not a media query, it nests. A .dark subtree
inside a light page is a self-contained dark island, color-scheme included:
<body>
<p class="text-fg-1">Light text.</p>
<aside class="dark bg-surface-1 text-fg-1">Dark island.</aside>
</body>color-scheme is set on :root and .dark so native form controls,
scrollbars, and the page canvas track the theme.
Tokens
Everything in the --color-* namespace generates the usual bg-*, text-*,
border-*, fill-*, ring-* utilities.
| Group | Tokens |
| --- | --- |
| Surfaces | --color-surface-1 -2 -3 --color-surface-accent |
| Foreground | --color-fg-1 -2 -3 --color-accent |
| Borders | --color-border |
| Buttons | --color-button --color-button-hover --color-button-fg |
| Scrollbar | --color-scrollbar --color-scrollbar-active |
| Links | --color-link --color-link-hover --color-decoration --color-decoration-hover |
| Tooltip | --color-tooltip --color-tooltip-fg |
| Status | --color-green -red -yellow -blue -orange -purple |
| Semantic | --color-alert --color-danger --color-surface-alert --color-surface-danger |
Surface and foreground ramps go from most to least prominent: surface-1 is
the page, surface-2 a raised panel, surface-3 a hover state; fg-1 is body
copy, fg-2 secondary, fg-3 disabled or hint text.
Tokens without utilities
| Group | Tokens |
| --- | --- |
| Shadows | --shadow-1 --shadow-2 |
| Effects | --lifted-brightness --backdrop-brightness |
These are plain custom properties, not @theme entries, and generate no
utilities by design.
Shadows are kept out of Tailwind's --shadow-* namespace because that
namespace inlines the literal shadow into the generated class (so
shadow-red-500 can rewrite its color). A shadow-2 utility built that way
would bake in the light shadow and ignore the .dark override entirely. Use
the variable, or Tailwind's late-binding arbitrary form:
<div class="shadow-(--shadow-2)">…</div>.card { box-shadow: var(--shadow-2); }The *-brightness tokens are multipliers for filter: brightness(), not
colors — a lifted surface brightens in dark mode (1.45) and is a no-op in
light (1); a scrim dims the backdrop behind a modal.
Overriding
Tokens are declared with @theme static, which means:
- Every token is emitted into
:root, even ones no utility references — so readingvar(--color-purple)from hand-written CSS always resolves. (Plain@themeprunes unused variables.) - Utilities compile to
var(--color-*)rather than inlining the literal value, so overriding a token at runtime — which is exactly what.darkdoes — also repaintsbg-*,text-*, andborder-*.
To retheme, override the variables after the import:
@import "tailwindcss";
@import "@bastianplsfix/colors/theme.css";
:root {
--color-accent: rebeccapurple;
}
.dark {
--color-accent: #c9a9ff;
}Note that --color-alert, --color-danger, --color-surface-alert,
--color-surface-danger, and (in dark) --color-tooltip are derived — they
color-mix() a status hue against --color-fg-1 or --color-surface-1. Change
--color-orange and the alert tokens follow. Override them directly and you
opt out of the blend.
Browser support
Everything degrades. The base layer is plain sRGB hex, and the
color-mix() and display-p3 refinements sit behind @supports, so an old
browser gets flat, correct colors rather than nothing.
License
MIT
