Think in roles, not raw colors
Instead of assigning raw colors directly to components (button = #2563eb), define semantic roles and map palette values to them:
Your components consume roles instead of hex codes, making it easy to change themes later — light/dark or brand variations — and keep colors consistent across the whole library.
A minimal Tailwind‑style base palette
The examples in this page are built from a simple palette: blue as the primary hue, gray as the neutral scale, and green/amber/red as semantic colors.
Buttons
Buttons are the most obvious place where primary/secondary roles show up.
Primary button — bg: blue-500, text: white, hover: blue-600
Secondary button — bg: white/gray-100, border: gray-200, text: gray-900
Ghost / tertiary — transparent, text: blue-600, hover: bg blue-50
Alerts and banners
For each status, use a very light tint for the background, a slightly stronger shade for the border, the main semantic color for the icon, and neutrals for text.
Success · Warning · Error · Info alerts
Badges and chips
Badges are compact — color must work at small sizes. Three common variants: solid (high emphasis), subtle (low emphasis) and outline.
Solid · Subtle · Outline badges
Chips, pills and toggles
Chips represent selections or filters. "Selected = some version of primary" is the consistent rule.
Unselected and selected chips
Toggle / switch — off: gray-200, on: blue-500, thumb: white
Inputs, text fields and forms
Inputs mostly use neutrals with an accent for focus and semantic colors for error/success.
Default · Focus · Error · Success input states
Building a simple theme system
Using the palette above, a minimal theme object ties everything together. Each component binds to roles — not hex codes — so the entire library can be rebranded by changing the values in one place.
const theme = {
bg: {
default: 'gray-50', // page background
surface: 'gray-100', // cards, panels
inset: 'white', // inputs, nested surfaces
},
text: {
primary: 'gray-900',
secondary:'gray-700',
inverted: 'white',
},
border: {
default: 'gray-200',
strong: 'gray-300',
focus: 'blue-500',
},
primary: {
bg: 'blue-500',
bgHover: 'blue-600',
subtleBg: 'blue-50',
text: 'white',
outline: 'blue-500',
},
status: {
success: { bg:'green-50', border:'green-200', icon:'green-500' },
warning: { bg:'amber-50', border:'amber-200', icon:'amber-500' },
error: { bg:'red-50', border:'red-200', icon:'red-500' },
info: { bg:'blue-50', border:'blue-200', icon:'blue-500' },
},
}What each component binds to
- Button —
theme.primary.* - Alert —
theme.status.*andtheme.text.* - Card —
theme.bg.surface,theme.border.default,theme.text.primary - Input —
theme.border.default/focus,theme.text.primary,theme.bg.inset
Because everything is role‑based, you can swap blue-500 for any other hue to rebrand, plug in Radix or Material colors instead of Tailwind values, or extend with dark‑theme counterparts like bg.default.dark and text.primary.dark.
Using Colorr as the source of truth
Colorr's collections give you a starting point for every role in the theme:
What you can do
- Pick a primary hue from any collection — Tailwind, Material, Radix, Chakra, Open Color.
- Choose neutral and semantic colors that complement it.
- Map them into theme tokens like in the example above.
- Reuse those tokens across your component library regardless of framework.