VisorVisor

Adapters

Generate framework-specific CSS from .visor.yaml themes using Visor's adapter layer.

Adapters transform theme engine output into CSS formatted for specific frameworks. Instead of a monolithic CSS bundle, adapters produce targeted output with proper @layer ordering, framework bridge tokens, or scoped selectors.

Available Adapters

NextJS

Generates a globals.css file with @layer declarations, Google Fonts @import, and dark mode support.

npx @loworbitstudio/visor theme apply .visor.yaml --adapter nextjs

Output includes:

  • @import url(...) for Google Fonts (when non-system fonts are specified)
  • @layer order declaration for specificity control
  • Primitives in @layer visor-primitives
  • Light/dark adaptive tokens in @layer visor-adaptive
  • FOWT prevention usage comment

next/font integration: If you use next/font for font loading, remove the @import lines from the generated CSS to avoid double-loading fonts. Configure fonts in your layout.tsx instead.

--scope-prefix (body-class theme swap)

By default, the nextjs adapter emits rules under :root, which works for whole-document theming. To let multiple themes coexist and switch via a body class (the body-class repaint pattern), pass --scope-prefix:

npx @loworbitstudio/visor theme apply themes/blacklight/theme.visor.yaml \
  --adapter nextjs \
  --scope-prefix 'body.mybrand-theme'

Generated output:

body.mybrand-theme {
  --surface-page: ...;
  /* primitives + light tokens */
}

body.mybrand-theme.dark,
body.mybrand-theme.theme-dark,
body.mybrand-theme[data-theme="dark"] {
  /* dark-mode overrides */
}

The dark-mode block composes the prefix with the standard .dark / .theme-dark / [data-theme="dark"] selectors, matching the body-class + html.dark dual-toggle pattern. The prefers-color-scheme: dark media query likewise composes the prefix with the existing :not(.light) guards.

When --scope-prefix is omitted, output is unchanged (:root selectors), so existing setups continue to work.

Fumadocs

Generates a bridge CSS file that maps Visor semantic tokens to fumadocs --color-fd-* custom properties.

npx @loworbitstudio/visor theme apply .visor.yaml --adapter fumadocs

This replaces the manual Section 4 (framework bridge) in theme CSS files. The bridge maps tokens like:

Fumadocs TokenVisor Source
--color-fd-background--surface-page
--color-fd-foreground--text-primary
--color-fd-card--surface-card
--color-fd-border--border-default
--color-fd-primary--surface-accent-default
--color-fd-ring--border-focus

Deck

Generates CSS scoped under a .deck--{theme-name} class for pitch decks where multiple themes coexist on one page.

npx @loworbitstudio/visor theme apply .visor.yaml --adapter deck

All tokens are nested under the scope class — no :root selectors. Dark mode uses .dark .deck--{name} selectors.

Flutter

Generates a complete Dart package with ThemeData for a .visor.yaml theme. Output includes token files (visor_colors.dart, visor_text_styles.dart, etc.) and a VisorAppTheme.light / .dark wrapper.

# Single theme
npx @loworbitstudio/visor theme apply .visor.yaml --adapter flutter --output packages/my-ui/

# All 11 Visor themes at once (stock + custom) → packages/visor_themes/
npm run themes:apply-flutter

The packages/visor_themes/ package aggregates all themes via the VisorThemes sealed class:

import 'package:visor_themes/visor_themes.dart';

MaterialApp(
  theme: VisorThemes.blackout.light,
  darkTheme: VisorThemes.blackout.dark,
);

Available theme getters: blackout, modernMinimal, neutral, space, blacklight, blacklightUnderground, entr, kaiah, referenceApp, solespark, veronica.

Each getter returns a VisorThemePair with .light and .dark ThemeData properties.

See Flutter Theme Consumption for full setup instructions.

CSS @layer Strategy

Adapter output uses CSS @layer declarations for specificity ordering:

@layer visor-primitives, visor-semantic, visor-adaptive, visor-bridge;

This ensures:

  • Primitives have lowest specificity
  • Semantic tokens override primitives
  • Adaptive (light/dark) tokens override semantic
  • Bridge tokens (fumadocs) override everything

No !important needed — layers handle specificity naturally.

Programmatic Usage

Adapters are available as functions from the theme engine package:

import { generateThemeData } from '@loworbitstudio/visor-theme-engine';
import { nextjsAdapter, fumadocsAdapter, deckAdapter } from '@loworbitstudio/visor-theme-engine/adapters';

const data = generateThemeData(yamlString);
const input = {
  primitives: data.primitives,
  tokens: data.tokens,
  config: data.config,
};

const nextjsCss = nextjsAdapter(input);
const fumadocsCss = fumadocsAdapter(input);
const deckCss = deckAdapter(input, { scopeClass: '.my-deck' });

FOWT Prevention

Flash of Wrong Theme (FOWT) occurs when a page loads with the default theme before JavaScript can apply the user's preferred theme. Visor provides a blocking script to prevent this.

import { FOWT_SCRIPT } from '@loworbitstudio/visor-theme-engine/fowt';

Add this as a blocking script in your document <head> before any stylesheets:

// In your Next.js layout.tsx
<head>
  <script>{FOWT_SCRIPT}</script>
</head>

The script reads localStorage('visor-theme'), falls back to prefers-color-scheme, and sets .dark or .light on <html> before first paint.

Custom Configuration

import { generateFowtScript } from '@loworbitstudio/visor-theme-engine/fowt';

// Custom localStorage key
const script = generateFowtScript({ storageKey: 'my-app-theme' });

// Force dark mode default
const darkScript = generateFowtScript({ defaultTheme: 'dark' });