The shared base layer of CSS custom properties (--base-*) used by all
KeenMate web components — web-multiselect,
web-daterangepicker,
web-grid, web-treeview, and more.
Each component ships its own prefixed variables (--ms-*, --drp-*, --wg-*, …)
that cascade from these --base-* values. Define the base layer once and every
component picks up a consistent, coordinated theme. Override a single --base-*
variable and the change propagates everywhere.
- Icons — four new affordance glyph tokens — added
--base-icon-refresh(refresh-cw, two curved arrows) for reload / re-fetch (consumers spin it with a CSS animation while a refresh is in flight),--base-icon-copy(two overlapping sheets) for copy-to-clipboard,--base-icon-ellipsis(three dots) for the "more / overflow" affordance — the vertical⋮is the same glyph rotated 90°, so no separate token — and--base-icon-save(floppy disk) for persist/commit. All four are mask-rendered Lucide glyphs and complete the affordance set@keenmate/pure-adminmigrated off Font Awesome;@keenmate/pure-cssmirrors all four as$base-icon-*.
- Icons — three new glyph tokens — added
--base-icon-filter(funnel) for filter affordances, and the checkbox/tree-node selection pair--base-icon-check(SELECTED — a tick) and--base-icon-indeterminate(PARTIALLY selected — the tri-state parent state). All three are mask-rendered Lucide glyphs like the rest of the icon set, and selection stays a distinct knob from disclosure (--base-icon-expand/--base-icon-collapse) so you can retarget one without moving the other.
npm install @keenmate/base-css-variablesImport the stylesheet once, as early as possible (before the component styles):
import '@keenmate/base-css-variables/base-variables.css';or in CSS / HTML:
@import '@keenmate/base-css-variables/base-variables.css';<link rel="stylesheet" href="node_modules/@keenmate/base-css-variables/base-variables.css">The base layer is a set of semantic design tokens, not raw colors. Several tokens may resolve to the same default value yet exist as separate variables so each role can be themed independently.
--base-main-bg and --base-input-bg both default to white (light) / near-black
(dark), but they mean different things:
| Token | Meaning | Example component consumers |
|---|---|---|
--base-main-bg |
The global / primary surface — the canvas an app shell, grid, dropzone or panel paints itself on | --wg-surface-1 (grid surface), --drp-primary-bg (calendar panel), --ms-hint-bg, --ms-actions-bg |
--base-input-bg |
The background of form fields specifically | --ms-input-bg, --drp-input-bg, --wg-input-bg |
Because they are separate variables, you can, for example, keep a grid or dropzone on a plain page background while giving input fields a subtly tinted fill:
:root {
--base-main-bg: #ffffff; /* page / grid / dropzone canvas */
--base-input-bg: #f7f9fc; /* inputs stand out slightly */
}If you only set --base-main-bg, inputs keep their own default — they don't
inherit from it. Set both when you want them to match.
Background surfaces layer outward from the canvas; each step is a little more prominent:
--base-main-bg → --base-elevated-bg → --base-hover-bg → --base-active-bg
- main — base canvas (page, grid, panel, dropzone)
- elevated — raised areas: headers, toolbars, dropdowns, popovers
- hover — pointer hover on a surface (rows, options)
- active — pressed / selected
- inverse — high-contrast surface, used for tooltips
Role-specific surfaces (--base-input-bg, --base-dropdown-bg, --base-tooltip-bg)
default to values in this scale but can be retargeted on their own — that's the
whole point of keeping them as distinct tokens.
--base-* is the single knob. This package is the canonical contract; its
consumers never define a parallel source of truth, they derive from it:
@keenmate/pure-cssmirrors this token list into SCSS ($base-*), adds theme derivation + its own--pc-*foundation tokens, and ships the grid / utilities / app-shell.@keenmate/pure-admin-corebuilds its component tokens (--pc-*) on top.- KeenMate web components read
--base-*directly (with inline fallbacks).
Every --pc-* is wired as var(--base-*, <fallback>), so overriding one
--base-* re-themes pure-admin components and the web components together.
Traced end-to-end for the accent:
LAYER 0 contract (THIS file, CSS) :root { --base-accent-color: #0ea5e9 }
│ pure-css mirrors the list into SCSS
LAYER 1 pure-css source (SCSS) $base-accent-color: #0ea5e9 !default; ◀─ a THEME overrides here
│ derive
LAYER 2 pure-css framework var (SCSS) $accent-color: $base-accent-color; (serves only as the build fallback)
│ emit (two mixins)
LAYER 3 pure-css emit ──▶ CSS --base-accent-color: #0ea5e9; ◀─ RAIL A · the knob
--pc-accent: var(--base-accent-color, #0ea5e9) ◀─ RAIL B · pure-admin's token
│
LAYER 4 pure-admin-core (CSS) --pc-accent-light, --pc-accent-hover,
--pc-link-color: var(--pc-accent), …
│
LAYER 5 consumers
pure-admin components background: var(--pc-accent-light);
web components --ms-accent-color: var(--base-accent-color, #3b82f6);
- RAIL A (
--base-accent-color) is the knob; RAIL B (--pc-accent) isvar(--base-accent-color, …), so at runtime it simply is the base value — the<fallback>only fires if RAIL A is ever missing (it isn't, once this file or a theme is loaded). - There is no
$pc-*SCSS variable. Thepclayer is born at emission as a CSS property pointing back at--base-*; nothing to author in SCSS.
| Override… | Where | Effect |
|---|---|---|
--base-accent-color |
any :root / .pc-mode-* / [data-*] scope (runtime) |
everything downstream, live — pure-admin and web components |
$base-accent-color |
pure-css SCSS (build) | the default baked into base.css + every --pc-* fallback |
--pc-accent |
a single --pc-* (runtime) |
pure-admin only — use for a deliberate pure-admin-only divergence |
That middle-less "one knob" row is the whole design: a theme (or a time-of-day
[data-daypart] scope) re-sets --base-* and the entire --pc-* layer re-resolves.
Override any --base-* variable in your own :root (or any scope) — the value
flows into every component:
:root {
--base-accent-color: #e11d48; /* rebrand every component's accent */
--base-main-bg: #fafafa;
--base-border-radius-md: 1; /* rounder corners everywhere */
}Colors are defined with CSS light-dark(),
so they follow the active color-scheme.
-
Automatic — the file sets
color-scheme: light darkon:root, so it follows the operating-system preference out of the box. -
Manual — force a theme on any subtree:
<html data-theme="dark"> <!-- or data-theme="light" -->
or set
color-scheme: light | darkon any element.
Naming is intentionally not 1:1 between the base layer and component variables.
For example --ms-primary-bg reads --base-hover-bg, and --drp-primary-bg reads
--base-main-bg. Always theme via the --base-* variables listed here.
| Variable | Purpose |
|---|---|
--base-accent-color |
Primary brand / action color |
--base-accent-color-hover |
Accent hover state |
--base-accent-color-active |
Accent active / pressed state |
--base-accent-color-light |
Subtle accent tint for backgrounds |
--base-accent-color-light-hover |
Subtle accent tint, hover |
| Variable | Purpose |
|---|---|
--base-main-bg |
Main surface (inputs, dropdowns) |
--base-elevated-bg |
Elevated surfaces: headers, toolbars, popovers |
--base-hover-bg |
Hover state for any surface (option/row hover) |
--base-active-bg |
Active / pressed surface |
--base-inverse-bg |
Inverse surface (fallback for tooltip background) |
| Variable | Purpose |
|---|---|
--base-text-color-1 |
Headers, titles, high-emphasis |
--base-text-color-2 |
Body text, labels |
--base-text-color-3 |
Secondary content, subtitles |
--base-text-color-4 |
Hints, placeholders, captions |
--base-text-color-on-accent |
Text on accent backgrounds |
--base-text-inverted |
Inverse of main text (on inverse / accent surfaces) |
| Variable | Purpose |
|---|---|
--base-border-color |
Standard border color |
--base-border |
Full border shorthand (1px solid …) |
| Variable | Purpose |
|---|---|
--base-input-bg |
Input background |
--base-input-color |
Input text color |
--base-input-border |
Input border (normal) |
--base-input-border-hover |
Input border on hover |
--base-input-border-focus |
Input border on focus |
--base-input-placeholder-color |
Placeholder text |
--base-input-bg-disabled |
Disabled input background |
--base-disabled-bg |
Disabled / readonly surface |
| Variable | Purpose |
|---|---|
--base-dropdown-bg |
Dropdown / popover background |
--base-dropdown-border |
Dropdown border |
--base-dropdown-box-shadow |
Dropdown shadow |
| Variable | Purpose |
|---|---|
--base-tooltip-bg |
Tooltip background |
--base-tooltip-text-color |
Tooltip text |
--base-tooltip-color |
Tooltip text alias (web-grid) |
| Variable | Purpose |
|---|---|
--base-<role>-color |
Role fill identity (vivid), role ∈ success/danger/warning/info |
--base-<role>-bg |
Solid role fill (= -color) |
--base-<role>-color-hover |
Role fill, hover |
--base-<role>-bg-light / -bg-subtle |
Subtle role tints |
--base-<role>-border |
Role border tint |
--base-<role>-text |
Role as foreground on a light surface (text/links) |
--base-text-on-<role> |
Readable text on the role fill |
--base-checkbox-border-color |
Checkbox border |
| Variable | Purpose |
|---|---|
--base-font-family |
Font stack |
--base-font-size-2xs … 2xl |
Font sizes (unitless multipliers) |
--base-font-weight-normal / medium / semibold |
Font weights |
--base-line-height-tight / normal / relaxed |
Line heights |
| Variable | Purpose |
|---|---|
--base-border-radius-sm / md / lg |
Corner radii (unitless multipliers) |
--base-input-size-xs…xl-height |
Standard input heights (unitless multipliers) |
Unitless multipliers: font sizes, radii and input heights are stored as plain numbers and combined by components with their rem scale — e.g.
calc(var(--base-font-size-base) * 0.1rem). This keeps sizing consistent across all KeenMate components while remaining scalable.
@keenmate/theme-designer— visual theme designer & generator that produces--base-*values from 3 input colors.
MIT © KeenMate