Theming ​

@sometic/theme is the runtime design-token and theme engine for Sometic. It owns token maps, CSS variable generation, system preference detection, WCAG contrast helpers (hex today), light/dark presets, and a framework-neutral theme controller that produces a stable ThemeSnapshot you can bind in any UI stack.

System standout: contrast, scoped brand, color-scheme

Applies CSS color-scheme with data-color-scheme. Use auditThemeContrast / assertThemeContrast, createScopedThemeController, and defineSemanticTokens. Sync document head via bindThemeToHead.

This section is the consumer guide for theming. Preference persistence lives on the Theme store bridge page; the underlying store primitives are documented under Store.

Overview ​

Sometic theming is behavior first, not a shipped visual kit:

  • You define tokens (color, space, radius, or your own categories).
  • The controller resolves mode, theme id, density, direction, high contrast, and reduced motion into one snapshot.
  • You apply that snapshot to document.documentElement or any scoped element via applyThemeToElement.
  • Your CSS (plain, Tailwind, Bootstrap, or anything else) reads CSS variables and data-* attributes. Sometic does not bundle Tailwind or Bootstrap.

When to use ​

  • Runtime light / dark / system switching across Vanilla, React, Vue, or other adapters
  • Token → CSS variable pipelines with a stable prefix
  • Scoped themes on a subtree (not only :root)
  • Density, RTL (dir), high-contrast, and reduced-motion flags as attributes
  • Preference persistence across reloads via @sometic/store/persistent

When not to use ​

  • Class / style / slot resolution only → @sometic/styling
  • Compile-time Figma → code token CI → use Style Dictionary (or similar) and feed results into defineTokens
  • A full visual CSS product as the deliverable → optional presets plus your own CSS
  • Framework-only providers with no shared engine → keep those at the app boundary; this package stays framework-neutral

Package map ​

SurfaceImportRole
Theme controller@sometic/themecreateThemeController, applyThemeToElement, snapshot types
Tokens@sometic/theme/tokensdefineTokens, mergeTokens, resolveToken
CSS variables@sometic/theme/css-variablestokensToCssVariables, serializeCssVariables
Contrast@sometic/theme/contrastHex parse, luminance, WCAG ratio helpers
System prefs@sometic/theme/systemColor scheme, reduced motion, more contrast (lazy matchMedia)
Presets@sometic/theme/presetsMinimal lightTheme / darkTheme for demos and tests

Presets stay on a subpath so apps that only need the controller do not pay for palettes. The controller root is about 4.4 KB gzip.

Architecture boundary ​

PackageOwnsDoes not own
@sometic/themeTokens, generation, mode resolution, attributes, contrast helpersClass merging, framework hooks
@sometic/stylingClasses, styles, slots, state attrs, resolveCssVariablesDesign tokens
@sometic/storePreference store, persistence adaptersTheme token math

Theme depends on styling’s resolveCssVariables and on store for subscriptions and optional persistence. Styling never owns tokens.

Quick start ​

ts
import { createThemeController, applyThemeToElement } from "@sometic/theme";
import { lightTheme, darkTheme } from "@sometic/theme/presets";

const theme = createThemeController({
    themes: [lightTheme, darkTheme],
    defaultThemeId: lightTheme.id,
    lightThemeId: lightTheme.id,
    darkThemeId: darkTheme.id,
    mode: "system",
});

applyThemeToElement(document.documentElement, theme.get());

theme.subscribe((snapshot) => {
    applyThemeToElement(document.documentElement, snapshot);
});

theme.setMode("dark");

CSS then consumes variables such as var(--sometic-color-primary) and attributes such as [data-color-scheme="dark"].

Snapshot shape ​

Every get() / subscribe callback receives a ThemeSnapshot:

FieldType (conceptually)Meaning
preferencesThemePreferencesRaw prefs: mode, themeId, density, direction, highContrast, reducedMotion
resolvedColorScheme"light" | "dark"Effective scheme after system resolution
resolvedThemeIdstringTheme actually used for tokens
tokensThemeTokensActive token map
cssVariablesRecord<string, string>Flat --prefix-category-key map
attributesRecord<string, string>data-theme, data-color-scheme, data-density, dir, optional contrast/motion flags

Docs in this section ​

PageWhat you will learn
InstallationInstall commands, peers, subpath imports
TokensdefineTokens, merge, resolve paths
ThemesRegister themes, mode vs theme id, presets
Runtime switchingController API, subscribe, dispose, scoped apply
CSS variablesNaming, serialize for SSR, apply cleanup
TailwindMap variables into Tailwind theme extensions
BootstrapDrive Bootstrap-styled UIs from variables and attrs
Plain CSSStyle with variables and state attributes only

Persistence ​

Pass persist: true with a storage adapter from @sometic/store/persistent. Defaults to in-memory storage unless you pass a web adapter, so durable browser prefs are explicit. Await hydrated before trusting restored values.

Deep dive: Theme store · Store

Contrast honesty ​

Contrast helpers parse hex colors (#rgb / #rrggbb) today. rgb(), hsl(), and OKLCH strings are not supported yet; invalid input fails closed (meetsWcagContrast → false, parseHexColor → undefined). Treat wider color spaces as a future extension, not current API.

Comparison (short) ​

ApproachFit
Hand-written :root variablesFine for static sites; Sometic adds registration, system mode, persistence, density/RTL, and a snapshot for any binding
next-themes / React-only providersGreat in Next.js; Sometic stays framework-neutral so Vanilla/Vue/Svelte share one engine
Style DictionaryCompile-time pipelines; use both: generate at build time, switch at runtime with this package