Concorde guide — Theming. Concorde components ship with default styles (fallback values in each component and in <sonic-theme>). Doc ID: docs/getting-started/theming. Keywords: Concorde, supersoniks, docs/getting-started/theming, theming, Theming, guide, , :root, theme="custom", --sc-*, theme="light", "dark", "auto", auto, prefers-color-scheme. URL: https://concorde.supersoniks.org/docs/getting-started/theming.
Theming
Theme-first customization
Concorde components ship with default styles (fallback values in each component and in <sonic-theme>).
You do not need to define any CSS variables to get a usable UI.
To align the kit with your brand, override only the essential theme tokens on <sonic-theme>, on :root, or in a project stylesheet.
Concorde derives radius steps, button/input rounding, shadows, and most per-component layout from these primitives—do not copy long lists of derived variables unless you have a specific need.
<sonic-theme color font >
<!-- your app -->
</sonic-theme>
import "@supersoniks/concorde/theme";
| Attribute | Effect |
|---|---|
(omit) | theme="custom" |
Default: core tokens + your --sc-*; no built-in light/dark palette |
theme="light" | "dark" | "auto" |
Opt-in presets; auto follows prefers-color-scheme |
class="bg-base block" (Tailwind) |
Page/surface background (--sc-base) |
color |
Text color (--sc-base-content) |
font |
Base font family/weight (--sc-font-family-base, etc.) |
Popovers, modals, and toasts mount under the nearest <sonic-theme>.
Customization example
Example (full brand override on <sonic-theme> or :root). Set every essential token below; derived radii and shadows still come from the theme engine—only --sc-rounded is listed here.
sonic-theme {
/* Semantic fills */
--sc-primary: #353429;
--sc-secondary: #f43f5e;
--sc-info: #7c3aed;
--sc-danger: #d71839;
--sc-warning: #f97316;
--sc-success: #10b981;
--sc-contrast: #111;
/* Text/icons on semantic fills */
--sc-primary-content: #fff;
--sc-secondary-content: #fff;
--sc-info-content: #fff;
--sc-danger-content: #fff;
--sc-warning-content: #fff;
--sc-success-content: #fff;
--sc-contrast-content: #fff;
/* Neutral fills */
--sc-base: #fff;
--sc-base-50: #f6f6f5;
--sc-base-100: #ededec;
--sc-base-200: #d7d7d4;
--sc-base-300: #c8c7c4;
--sc-base-400: #a8a7a2;
--sc-base-500: #94938c;
--sc-base-600: #76756c;
--sc-base-700: #59584e;
--sc-base-800: #47463b;
--sc-base-900: #353429;
/* Text/icons on neutral surfaces */
--sc-base-content: var(--sc-base-900);
/* Typography */
--sc-font-family-base: "Oswald", system-ui, -apple-system, "Segoe UI", Roboto,
"Helvetica Neue", Arial, sans-serif;
--sc-headings-font-family: "alternate-gothic-compressed", system-ui, sans-serif;
--sc-headings-line-height: 1.1;
--sc-headings-font-weight: bold;
--sc-headings-text-transform: none;
/* Shape (primitive only) */
--sc-rounded: 0;
/* Forms */
--sc-border-width: 0.2rem;
--sc-form-height: 2em;
--sc-input-bg: var(--sc-base);
--sc-input-color: var(--sc-base-content);
--sc-input-border-color: var(--sc-input-color);
--sc-input-active-bg: var(--sc-primary);
--sc-input-active-color: var(--sc-primary-content);
--sc-input-active-border-color: var(--sc-primary);
}
Without these overrides, components keep Concorde’s per-component fallbacks. Built-in light/dark palettes apply only when you set theme="light", theme="dark", or theme="auto".
What we intentionally avoid: large public --sc-alert-* / --sc-badge-* APIs or ::part() styling hooks on most components. Prefer attributes (type, status, size, …) and the theme tokens above.
Only essential CSS variables (public theme API)
These are the smallest set we recommend customizing. Everything else should stay internal to the theme or to components (--sc_* private variables, attributes like type, size, variant).
Surfaces and foreground
Tokens without a -content suffix are fill / background colors.
Tokens with -content are text and icons on top of the matching fill (solid buttons, badges, alerts, etc.).
Neutral fills
| Variable | Role |
|---|---|
--sc-base |
Main surface / page background |
--sc-base-50 … --sc-base-900 |
Neutral scale (borders, subtle backgrounds, muted UI) |
Neutral foreground
| Variable | Role |
|---|---|
--sc-base-content |
Default body text and icons on neutral surfaces |
Semantic fills
| Variable | Role |
|---|---|
--sc-primary |
Primary actions, links, emphasis |
--sc-secondary |
Secondary brand accent (when used) |
--sc-info, --sc-success, --sc-warning, --sc-danger |
Status and feedback |
--sc-contrast |
High-contrast fills (e.g. on media) |
Semantic foreground
| Variable | Role |
|---|---|
--sc-primary-content, --sc-secondary-content, … |
Text/icons on the matching semantic fill (--sc-primary, etc.) |
Shape
| Variable | Role |
|---|---|
--sc-rounded |
Single primitive corner radius; the theme computes --sc-rounded-sm, --sc-btn-rounded, --sc-input-rounded, etc. |
Typography
| Variable | Role |
|---|---|
--sc-font-family-base |
Body UI text |
--sc-font-weight-base, --sc-font-style-base |
Optional |
--sc-headings-font-family, --sc-headings-line-height, --sc-headings-font-weight, --sc-headings-text-transform |
Headings (modal titles, etc.) |
Forms (only if you customize inputs globally)
Interactive controls expose a few extra theme-level tokens; prefer these over styling individual inputs from outside.
| Variable | Role |
|---|---|
--sc-border-width |
Default border thickness |
--sc-form-height |
Control height |
--sc-input-bg, --sc-input-color, --sc-input-border-color |
Default field chrome |
--sc-input-active-bg, --sc-input-active-color, --sc-input-active-border-color |
Focus/active state |
Tailwind in new components ?
Set tailwind.config.js to match concorde style token in your custom component.
...
theme: {
borderRadius: {
DEFAULT: "var(--sc-rounded)",
none: "0",
sm: "var(--sc-rounded-sm)",
md: "var(--sc-rounded-md)",
lg: "var(--sc-rounded-lg)",
xl: "var(--sc-rounded-xl)",
full: "9999px",
},
boxShadow: {
DEFAULT: "var(--sc-shadow)",
none: "none",
sm: "var(--sc-shadow-sm)",
md: "var(--sc-shadow-md)",
lg: "var(--sc-shadow-lg)",
xl: "var(--sc-shadow-xl)",
},
colors: {
transparent: "transparent",
current: "currentColor",
content: "var(--sc-base-content)",
base: "var(--sc-base)",
neutral: {
0: "var(--sc-base)",
50: "var(--sc-base-50)",
100: "var(--sc-base-100)",
200: "var(--sc-base-200)",
300: "var(--sc-base-300)",
400: "var(--sc-base-400)",
500: "var(--sc-base-500)",
600: "var(--sc-base-600)",
700: "var(--sc-base-700)",
800: "var(--sc-base-800)",
900: "var(--sc-base-900)",
content: "var(--sc-base)",
},
primary: {
DEFAULT: "var(--sc-primary)",
content: "var(--sc-primary-content)",
},
success: {
DEFAULT: "var(--sc-success)",
content: "var(--sc-success-content)",
},
danger: {
DEFAULT: "var(--sc-danger)",
content: "var(--sc-danger-content)",
},
warning: {
DEFAULT: "var(--sc-warning)",
content: "var(--sc-warning-content)",
},
info: {
DEFAULT: "var(--sc-info)",
content: "var(--sc-info-content)",
},
contrast: {
DEFAULT: "var(--sc-contrast)",
content: "var(--sc-contrast-content)",
},
},
extend: {
borderWidth: {
DEFAULT: "var(--sc-border-width)",
},
fontFamily: {
headings: ["var(--sc-headings-font-family, var(--sc-font-family-base, sans-serif))"],
body: ["var(--sc-font-family-base)"],
monospace: ["var(--sc-font-family-monospace, monospace)"],
}
}