Concorde documentation · same URL (interactive) · llms.txt

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);
}

Palette generator: concordecolors

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)"],
    }
  }