OSS-first docs
These docs teach the open system first: contracts, generated surfaces, runtimes, governance, and incremental adoption. Studio shows up as the operating layer on top, not as the source of truth.
ThemeSpec guide
Set up ContractSpec Theme
Use ContractSpecEditorialTheme as the versioned source of truth for OSS consumers who want the same warm light palette and landing-aligned dark mode used across ContractSpec web surfaces.
Setup checklist
✓ Install @lssm-tech/lib.contracts-spec and @lssm-tech/lib.design-system.
✓ Import ContractSpecEditorialTheme instead of copying ad-hoc token maps.
✓ Apply the light variables to :root and the dark overlay to .dark.
✓ Let next-themes or your app shell own the html class switch.
✓ Validate the ThemeSpec in CI before publishing tenant overrides.
1) Import the contract theme
The theme lives in packages/libs/contracts-spec/src/themes.ts. Consumers should reference the exported spec, registry, and CSS variable helpers rather than duplicating tokens in app code.
import {
ContractSpecEditorialTheme,
ContractSpecEditorialThemeBodyBackground,
ContractSpecEditorialThemeCssVariables,
ThemeRegistry,
validateThemeSpec,
} from '@lssm-tech/lib.contracts-spec/themes';
const registry = new ThemeRegistry([ContractSpecEditorialTheme]);
const validation = validateThemeSpec(ContractSpecEditorialTheme);
if (!validation.valid) {
throw new Error('ContractSpec theme is not publishable');
}
export const contractSpecThemeRef = {
key: 'contractspec.editorial',
version: '1.0.0',
} as const;
export const pageBackground = ContractSpecEditorialThemeBodyBackground;
export const cssVariables = ContractSpecEditorialThemeCssVariables;
export const theme = registry.get(
contractSpecThemeRef.key,
contractSpecThemeRef.version
);2) Bridge it into Tailwind and UI
Use the design-system theme bridge when you need Tailwind presets, CSS text, runtime variables, or mode-aware component defaults.
import {
DesignSystemThemeProvider,
resolveThemeModeTokens,
themeSpecToCssVariables,
themeSpecToTailwindCss,
themeSpecToTailwindPreset,
} from '@lssm-tech/lib.design-system/theme';
import { ContractSpecEditorialTheme } from '@lssm-tech/lib.contracts-spec/themes';
export const lightTokens = resolveThemeModeTokens(
ContractSpecEditorialTheme,
'light'
);
export const darkTokens = resolveThemeModeTokens(
ContractSpecEditorialTheme,
'dark'
);
export default themeSpecToTailwindPreset(lightTokens);
export const cssText = themeSpecToTailwindCss(
themeSpecToCssVariables(ContractSpecEditorialTheme),
{ includeCustomVariant: true }
);
export function ContractSpecThemeProvider({
children,
mode = 'light',
}: {
readonly children: React.ReactNode;
readonly mode?: 'light' | 'dark';
}) {
return (
<DesignSystemThemeProvider
theme={ContractSpecEditorialTheme}
mode={mode}
applyCssVariables
>
{children}
</DesignSystemThemeProvider>
);
}3) Publish light and dark CSS
If your app owns global CSS directly, mirror the same semantic variables. The landing app uses these light variables plus the dark overlay from the contract theme.
/* Source of truth:
ContractSpecEditorialTheme in packages/libs/contracts-spec/src/themes.ts */
:root {
--paper: #f7f0e6;
--paper-muted: #efe4d3;
--paper-panel: #fbf7f0;
--ink: #1d1b18;
--ink-soft: #5e564d;
--line: #d2c3ae;
--rust: #a24f2a;
--rust-strong: #854120;
--amber: #d8a24a;
--blue: #255678;
--success: #2d6a4f;
--background: var(--paper);
--foreground: var(--ink);
--card: var(--paper-panel);
--card-foreground: var(--ink);
--primary: var(--rust);
--primary-foreground: #fff8f0;
--secondary: var(--paper-muted);
--secondary-foreground: var(--ink);
--muted: color-mix(in srgb, var(--paper-muted) 78%, #ffffff 22%);
--muted-foreground: var(--ink-soft);
--accent: var(--blue);
--accent-foreground: #eef7fb;
--border: var(--line);
--ring: color-mix(in srgb, var(--rust) 60%, #ffffff 40%);
--radius: 1.25rem;
}
.dark {
--background: #161412;
--foreground: #f5ede2;
--card: #211d19;
--card-foreground: #f5ede2;
--primary: #c46c43;
--primary-foreground: #fff8f0;
--secondary: #2c2723;
--secondary-foreground: #f5ede2;
--muted: #322c27;
--muted-foreground: #cfbeb0;
--accent: #79a8c6;
--accent-foreground: #101418;
--border: #433a34;
--ring: #c46c43;
}4) Wire the class switch
ContractSpec keeps the token contract separate from the app-level theme switch. In Next.js, use next-themes to toggle the light / dark class and let CSS variables do the rendering work.
import { ThemeProvider } from 'next-themes';
export function Providers({ children }: { readonly children: React.ReactNode }) {
return (
<ThemeProvider
attribute="class"
defaultTheme="light"
enableSystem
disableTransitionOnChange
>
{children}
</ThemeProvider>
);
}Parity with this monorepo
web-landing
Light: :root uses the editorial paper, ink, rust, amber, and blue tokens.
Dark: .dark matches ContractSpecEditorialTheme.modes.dark.
web-application-monolith
Light: :root is locked by tests to ContractSpecEditorialThemeCssVariables and the editorial body gradient.
Dark: .dark is a managed cockpit overlay today; use the contract dark overlay when strict landing parity is required.
Verify adoption
Add a small test like the monolith theme contract test: read your global CSS, assert each exported ContractSpecEditorialThemeCssVariables entry is present, and assert the layout metadata publishes #f7f0e6 as the light browser theme color.
Validation and typing
Keep runtime validation and TypeScript types aligned from the same source definitions.
Build entity surfaces
Implement searchable entity lists, detail panels, edit slots, RichRef-aware fields, RoleMorph adaptation, and personalization from DataView contracts.
Why ContractSpec
Keep educational and comparison content reachable without letting it define the primary OSS learning path.