@mainset/ui-core
v0.1.0
Published
Basic SCSS, TypeScript and JS configurations for mainset UI
Readme
UI Core Design Tokens & Theming
The best and most scalable strategy for handling color tokens in a modern UI kit is using a multi-tiered design token system (often referred to as an "Aliasing" or "Semantic" system).
Standardizing on just literal names (blue-500) or just context names (primary-500) both present problems at scale. Instead, the industry standard (used by Material Design, Atlassian, GitHub, and Figma) is to use both in a strict hierarchy.
The 3-Tier Strategy
Here is the 3-Tier strategy that will give you infinite flexibility and perfect multi-theme support:
Level 1: Primitive Tokens (The Palette)
These are your literal, literal base colors. They describe exactly what the color is, completely independent of how it is used. They should never change meaning.
- Naming:
color-[name]-[scale] - Examples:
--blue-100,--blue-500,--gray-900,--red-500. - Rule: Never use these directly in your component CSS. If you put
background: var(--blue-500)in aButton, it's impossible to switch that button to "Purple" in a new theme without overriding the button's internal CSS.
Level 2: Semantic Tokens (The Intent / Context)
This is the magic layer. Semantic tokens describe how or where the color is used, but not what the actual color is. They act as a bridge, pointing to your Primitive Tokens.
- Naming:
color-[category]-[variant/state] - Examples:
--color-text-primary,--color-bg-surface,--color-border-subtle,--color-action-primary-hover,--color-feedback-danger. - Rule: This is what you use in your CSS Modules.
Level 3: Component Tokens (Optional, but great for UI Kits)
For ultimate flexibility in a commercial UI kit, you map Semantic Tokens to specific Component Tokens.
- Naming:
[component]-[element]-[state] - Examples:
--button-bg-primary-hover
How this actually works for Multiple Themes
The beauty of this system is that when you switch themes (Light vs. Dark, or Brand A vs. Brand B), you only change the Level 2 (Semantic) pointers. Your components never change.
/* 1. Primitive Tokens (Never change) */
:root {
--blue-500: #3b82f6;
--blue-600: #2563eb;
--gray-50: #f9fafb;
--gray-900: #111827;
--red-500: #ef4444;
}
/* 2. Semantic Tokens - DEFAULT (Light Theme) */
:root,
[data-theme='light'] {
--color-bg-base: var(--gray-50);
--color-text-primary: var(--gray-900);
--color-action-primary: var(--blue-500);
--color-action-primary-hover: var(--blue-600);
--color-feedback-error: var(--red-500);
}
/* 2. Semantic Tokens - DARK THEME */
[data-theme='dark'] {
/* We flip the background and text... */
--color-bg-base: var(--gray-900);
--color-text-primary: var(--gray-50);
/* Action colors might stay the same, or shift to a lighter blue */
--color-action-primary: var(--blue-600);
}
/* 2. Semantic Tokens - "HALLOWEEN" THEME */
[data-theme='halloween'] {
/* The layout remains identical, but the meaning of "primary" changes */
--color-action-primary: var(--orange-500);
--color-bg-base: var(--black);
}Then, in your React CSS Module (Button.module.scss):
.button {
background-color: var(--color-action-primary);
color: var(--color-text-primary);
/* The button doesn't know if it's blue, orange, light, or dark.
It just knows it needs the "Primary Action" color. */
}
.button:hover {
background-color: var(--color-action-primary-hover);
}Why primary-500 is a trap
Many frameworks use primary-100 through primary-900. But what exactly is primary-100 used for? Is it a background for a badge? Is it text?
If primary-900 goes on primary-100 in light mode, does that invert in dark mode (where primary-100 text goes on a primary-900 background)? You end up writing crazy CSS rules trying to invert scales.
By using exact semantic intents (--color-text-primary-on-brand, --color-bg-surface), Dark Mode just means pointing that variable to a different Primitive Hex code.
Recommended Naming Taxonomy
A great format used by many enterprise teams is:
[Category]-[Concept]-[Property]-[Variant]-[State]
- Category:
color,space,font - Concept:
action,feedback,surface,text,border - Property/Context:
primary,secondary,success,danger - State:
default(implied),hover,active,disabled
Examples:
color-text-dangercolor-bg-surface-hovercolor-border-subtle
1. Category (The "What")
The foundational type of the design token.
color: Hex, RGB, or HSL values.font: Typography properties (often split intofont-size,font-weight,font-family,line-height).space: Padding, margins, layout.radius: Border-radius.elevation: Box-shadows or z-index.opacity: Alpha transparency.duration: Animation or transition timings.
2. Concept (The "Where")
The structural UI element the category is applied to.
bg(orsurface): Backgrounds of containers, cards, and pages.text(orfg): Typography and text content.border(orstroke): Outlines, dividers, and component borders.icon: SVGs and glyphs.action: Interactive elements like buttons, tabs, or links.feedback: Alerts, toasts, and validation states.
3. Property / Context (The "Intent")
The semantic meaning or brand role of the element.
- Brand Roles:
primary,secondary,tertiary,brand. - Neutral Roles:
neutral,base,inverse(e.g., white text on dark bg). - Feedback Roles:
success(green),danger/error(red),warning(yellow),info(blue).
4. Variant (The "Scale or Prominence")
The magnitude, emphasis, or numeric scale of the token.
- Emphasis:
subtle,strong,muted,bold,heavy. - T-Shirt Sizes (for fonts, spacing, radius):
xxs,xs,sm,md,lg,xl,xxl. - Numeric Scale (often for raw colors or weights):
100,200,300...900.
5. State (The "Interaction")
The user pseudo-class. If it is the default state, this slot is completely omitted.
- (default)
hover: Mouse over.active: Mouse pressed down / tapping.focus: Keyboard navigation focus.disabled: Non-interactive state.selected: Active tab or checked checkbox.
