W WRNexusJS
Frontend

@wrnexus/styles

CSS pipeline, themes, fonts, profiles, and application config.

bun add @wrnexus/styles@0.2.15
Global CSS bundling, the --wire-* design-token theme system, and the wrnexus.config.ts app-config loader for WRNexusJS apps.

Part of the WRNexusJS framework — an SSR-first, Bun-native full-stack web framework.

Overview

This package owns three server-side concerns that shape every page a WRNexusJS app renders:

1. Global stylesheet pipeline — finds app/styles/global.css (or aggregates app/styles/*.css), bundles it with Bun's CSS bundler (which resolves @import, including from node_modules), and produces one stylesheet that is <link>ed into every page's <head>. Because it is a plain global sheet, it styles server-rendered markup and hydrated client islands identically. A custom process hook lets you swap in Tailwind / PostCSS / Sass. 2. Theme system — design tokens exposed as CSS custom properties (--wire-<key>), with built-in light/dark sets, deep-merged user overrides, an SSR <html data-theme> render (no flash), and a tiny client runtime to toggle/persist the choice. 3. App config — loads wrnexus.config.ts (the AppConfig type), applies named profile overrides, and loads the .env cascade.

It runs server-side / at build time. Reach for it when configuring an app, defining themes, or customising how global CSS is produced.

Installation

bun add @wrnexus/styles
Private package — the machine must be authenticated to the wrnexus npm org
(a read token in ~/.npmrc). Requires Bun (Node is not supported).

API

Everything is exported from the package root (@wrnexus/styles).

Config loading

ExportSignaturePurpose
loadAppConfig(appRoot: string, profile?: string) => Promise<AppConfig>Load wrnexus.config.* with the active profile deep-merged in (profiles stripped from the result).
loadRawConfig(appRoot: string) => Promise<AppConfig>Load the raw config with the profiles map intact; returns {} if no config file exists.
resolveProfile(options?: { explicit?; mode? }) => stringResolve the active profile: explicit arg > WRNEXUS_PROFILE env var > mode-based default (production in prod, else development).
loadEnv(appRoot: string, profile: string) => Record<string, string>Load the .env cascade for a profile into process.env without clobbering real env vars. Returns what it loaded.
headToString`(head?: string \string[]) => string`Flatten AppConfig.head into a single HTML string.

Config file names probed, in order: wrnexus.config.ts, wrnexus.config.js, wrnexus.config.mjs.

.env cascade precedence (low → high): .env < .env.<profile> < .env.local < .env.<profile>.local. Variables already present in the real environment always win.

AppConfig

The type of the object your wrnexus.config.ts default-exports. Every field is optional.

FieldTypeDescription
head`string \string[]`Raw HTML appended to every page's <head> (e.g. CDN stylesheet/script links).
seoSeoConfigGlobal SEO defaults, merged with each page's exported meta. (from @wrnexus/core)
securitySecurityConfigFramework security headers and optional CORS policy. (from @wrnexus/core)
stylesStylesConfigGlobal stylesheet pipeline config (see below).
themeThemeConfigDesign-token themes, deep-merged over the built-in light/dark.
i18n{ default?: string; locales?: string[] }Default language + supported locales (strings live in app/locales/*.json).
db`{ driver: "sqlite" \"postgres" \"mysql" \"mongo"; url: string }`Default database connection; reached with getDb().
databasesRecord<string, { driver; url }>Additional named databases, reached with getDb("<name>"); each has its own app/db/<name>/ migrations/queries.
realtime{ scale?: boolean; redisUrl?: string }When scale is true (or redisUrl is set), room broadcasts bridge over Redis pub/sub so they reach clients on every app process.
portnumberDefault server port.
profilesRecord<string, Partial<Omit<AppConfig, "profiles">>>Named profiles (dev, prod, uat, test, …). The active profile's overrides are deep-merged over the base config. Selected via --profile=<name> or WRNEXUS_PROFILE.

Styles pipeline

ExportSignaturePurpose
findStyleEntry`(appDir, appRoot, override?) => string \null`Resolve the CSS entry: override (relative to appRoot) → app/styles/global.css → an aggregate of all app/styles/*.css (written to app/.wrnexus/styles-entry.css). null if the app has no styles.
bundleCss(entryPath: string, mode: Mode) => Promise<string>Bundle an entry with Bun.build (CSS bundler). Resolves @import (local + node_modules), handles nesting, minifies when mode === "production".
renderStyles(ctx: StyleProcessContext, styles?: StylesConfig) => Promise<string>Produce final CSS: runs styles.process(ctx) if provided, else bundleCss. Returns "" when ctx.entryPath is null.

StylesConfig:

interface StylesConfig {
  /** CSS entry path relative to the app root. Default: app/styles/global.css */
  entry?: string;
  /** Custom processor — return the final CSS string (Tailwind/PostCSS/Sass). */
  process?: (ctx: StyleProcessContext) => string | Promise<string>;
}

interface StyleProcessContext {
  entryPath: string | null; // resolved absolute CSS entry, or null
  appDir: string;
  appRoot: string;
  mode: Mode; // "development" | "production"
}

Theme system

ExportType / SignaturePurpose
DEFAULT_THEMESRecord<string, ThemeTokens>Built-in light and dark token maps.
THEME_COOKIE"wire-theme"Cookie the resolved theme is read from / persisted to.
THEME_CSS_HREF"/__wrnexus/theme.css"URL the generated theme stylesheet is served at.
THEME_JS_HREF"/__wrnexus/theme.js"URL the client theme runtime is served at.
resolveThemeConfig(config?: ThemeConfig) => ResolvedThemeDeep-merge the user's theme config over the defaults; pick the default theme (config's default if valid, else dark, else the first).
resolveThemeName`(cookieValue: string \undefined, theme: ResolvedTheme) => string`Pick a valid theme name from a cookie, falling back to theme.default.
renderThemeCss(theme: ResolvedTheme) => stringGenerate the theme stylesheet: a :root{…} default plus one [data-theme="<name>"]{…} block per theme.
renderThemeRuntime(theme: ResolvedTheme) => stringGenerate the client runtime (see below).

Tokens are emitted as --wire-<key> custom properties, except the reserved key color-scheme, which is emitted as the native color-scheme CSS property so form controls and scrollbars match the theme.

ThemeConfig / ThemeTokens / ResolvedTheme:

type ThemeTokens = Record<string, string>;

interface ThemeConfig {
  default?: string; // theme used when no cookie is present
  themes?: Record<string, ThemeTokens>; // deep-merged over built-in light/dark
}

interface ResolvedTheme {
  default: string;
  names: string[];
  themes: Record<string, ThemeTokens>;
}

Built-in token keys (both light and dark): color-scheme, color-bg, color-surface, color-surface-2, color-text, color-muted, color-border, color-primary, color-primary-hover, color-primary-contrast, color-danger, color-success, color-warning, radius, radius-sm, font-sans, shadow-1.

The client runtime (renderThemeRuntime) exposes window.wireTheme with { get, set, toggle, bind, themes }, wires up any [data-wire-theme-toggle] and [data-wire-theme-set] elements on load, and persists the choice to the wire-theme cookie (max-age 1 year, samesite=lax). toggle() cycles through the configured theme names in order.

Usage

wrnexus.config.ts

import type { AppConfig } from "@wrnexus/styles";

export default {
  head: [
    '<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@5/dist/css/bootstrap.min.css">',
  ],
  port: 3000,
  db: { driver: "sqlite", url: "app.db" },
  theme: {
    default: "dark",
    themes: {
      light: { "color-primary": "#7c3aed" }, // override one token; rest inherited
      brand: {
        // add a whole new theme
        "color-scheme": "dark",
        "color-bg": "#0a0a0a",
        "color-primary": "#22d3ee",
      },
    },
  },
  styles: {
    entry: "app/styles/main.css",
  },
  profiles: {
    production: {
      db: { driver: "postgres", url: process.env.DATABASE_URL! },
    },
  },
} satisfies AppConfig;

Loading config + producing CSS

import {
  loadAppConfig,
  resolveProfile,
  loadEnv,
  findStyleEntry,
  renderStyles,
} from "@wrnexus/styles";

const appRoot = process.cwd();
const mode = "production" as const;

const profile = resolveProfile({ mode });
loadEnv(appRoot, profile);

const config = await loadAppConfig(appRoot, profile);

const appDir = `${appRoot}/app`;
const entryPath = findStyleEntry(appDir, appRoot, config.styles?.entry);
const css = await renderStyles({ entryPath, appDir, appRoot, mode }, config.styles);

Rendering the theme

import {
  resolveThemeConfig,
  resolveThemeName,
  renderThemeCss,
  renderThemeRuntime,
  THEME_COOKIE,
} from "@wrnexus/styles";

const theme = resolveThemeConfig(config.theme);

// Server: pick the active theme from the request cookie (no flash).
const active = resolveThemeName(cookies[THEME_COOKIE], theme);
// → render <html data-theme={active}>

const themeCss = renderThemeCss(theme); // served at THEME_CSS_HREF
const themeJs = renderThemeRuntime(theme); // served at THEME_JS_HREF

In templates, consume tokens via the custom properties:

.card {
  background: var(--wire-color-surface);
  color: var(--wire-color-text);
  border: 1px solid var(--wire-color-border);
  border-radius: var(--wire-radius);
  box-shadow: var(--wire-shadow-1);
}
<button data-wire-theme-toggle>Toggle theme</button>
<button data-wire-theme-set="brand">Brand theme</button>

Requirements / Notes

  • Bun-only. bundleCss uses Bun.build's CSS bundler for @import resolution, nesting, and minification. Node is not supported.
  • Config and env loading use node:fs / node:path / node:url and read from process.env.
  • Peer package: @wrnexus/core supplies the SeoConfig and SecurityConfig types referenced by AppConfig.
  • The bundled global stylesheet, the theme stylesheet (THEME_CSS_HREF), and the theme runtime (THEME_JS_HREF) are wired into pages by the framework's server; this package only produces their contents.

Complete TypeScript API

This declaration is generated from the exact published package and lists its exported functions, classes, interfaces, and types.

import { SeoConfig, SecurityConfig } from '@wrnexus/core';
import { StorageConfig } from '@wrnexus/uploader';

/**
 * Theme system — design tokens that work SSR and client-side.
 *
 * Tokens are plain CSS custom properties (`--wire-<key>`) so they cascade and
 * can be overridden by user CSS. Each theme is a flat token map; the framework
 * ships default `light`/`dark` sets and the user's config deep-merges over them.
 *
 * The server renders `<html data-theme="…">` from the `wire-theme` cookie (no
 * flash), and a tiny client runtime toggles/persists it. The reserved token key
 * `color-scheme` is emitted as the native CSS property (not a variable) so form
 * controls and scrollbars match the theme.
 */
type ThemeTokens = Record<string, string>;
interface ThemeConfig {
    /** Name of the theme used when no `wire-theme` cookie is present. */
    default?: string;
    /** Named token maps. Deep-merged over the framework's built-in light/dark. */
    themes?: Record<string, ThemeTokens>;
}
interface ResolvedTheme {
    default: string;
    names: string[];
    themes: Record<string, ThemeTokens>;
}
/** Cookie the resolved theme is read from / persisted to. */
declare const THEME_COOKIE = "wire-theme";
declare const THEME_CSS_HREF = "/__wrnexus/theme.css";
declare const THEME_JS_HREF = "/__wrnexus/theme.js";
/** Built-in themes so components have tokens out of the box. */
declare const DEFAULT_THEMES: Record<string, ThemeTokens>;
/** Merge the user's theme config over the built-in defaults. */
declare function resolveThemeConfig(config?: ThemeConfig): ResolvedTheme;
/** Pick a valid theme name from a cookie value, falling back to the default. */
declare function resolveThemeName(cookieValue: string | undefined, theme: ResolvedTheme): string;
/** Generate the theme stylesheet: a `:root` default plus one block per theme. */
declare function renderThemeCss(theme: ResolvedTheme): string;
/**
 * Generate the client theme runtime. It exposes `window.wireTheme` and binds
 * `[data-wire-theme-toggle]` / `[data-wire-theme-set]` elements. The configured
 * theme names are baked in so `toggle()` cycles through them in order.
 */
declare function renderThemeRuntime(theme: ResolvedTheme): string;

/**
 * Font configuration.
 *
 * Declare fonts in `wrnexus.config.ts` under `fonts` and the framework emits
 * optimized `<head>` markup for you:
 *   - Google Fonts: `preconnect` hints + a single subsetted stylesheet request
 *     (only the weights you list) with `font-display`. The CSP is auto-extended
 *     so the fonts load under the default security policy (see loadAppConfig).
 *   - Self-hosted fonts: generated `@font-face` rules + optional `<link rel=preload>`
 *     for above-the-fold text (the fastest, no-third-party option).
 *   - Family stacks: `sans`/`mono`/`serif` become `--wrn-font-*` CSS variables,
 *     and `sans` is applied to `body`.
 */
type FontDisplay = "auto" | "block" | "swap" | "fallback" | "optional";
interface GoogleFont {
    /** Family name as it appears on fonts.google.com, e.g. "Inter". */
    family: string;
    /** Weights to load — ONLY these are fetched. Default: [400]. */
    weights?: (number | string)[];
    /** Also load italic styles for each weight. */
    italic?: boolean;
    /** Per-font `font-display` override (else the config default). */
    display?: FontDisplay;
}
interface LocalFontFace {
    /** `font-family` name this face defines. */
    family: string;
    /** URL to the font file, typically served from `public/` (e.g. "/fonts/inter.woff2"). */
    src: string;
    /** e.g. 400, "700", or "100 900" for a variable font. Default: 400. */
    weight?: number | string;
    style?: "normal" | "italic";
    /** CSS `src` format; inferred from the file extension when omitted. */
    format?: string;
    display?: FontDisplay;
    /** Emit `<link rel="preload" as="font">` — use for the primary above-the-fold face. */
    preload?: boolean;
    /** Optional `unicode-range` subset. */
    unicodeRange?: string;
}
interface FontConfig {
    /** Google Fonts, loaded with preconnect + weight subsetting + `font-display`. */
    google?: GoogleFont[];
    /** Self-hosted `@font-face` definitions (files served from `public/`). */
    local?: LocalFontFace[];
    /** Default `font-display` for faces that don't set their own. Default: "swap". */
    display?: FontDisplay;
    /** Body / default family stack → `--wrn-font-sans` + `body { font-family }`. */
    sans?: string;
    /** Monospace family stack → `--wrn-font-mono`. */
    mono?: string;
    /** Serif family stack → `--wrn-font-serif`. */
    serif?: string;
}
/**
 * Render all `<head>` markup for a font config. Returns "" when nothing is
 * configured. The output is trusted, framework-controlled HTML.
 */
declare function renderFontHead(fonts?: FontConfig): string;
/**
 * Production variant that inlines the small Google Fonts stylesheet at build
 * time. This removes a render-blocking CSS round trip while retaining the same
 * font files, `font-display`, CSP sources, and offline-safe fallback markup.
 */
declare function renderProductionFontHead(fonts?: FontConfig, fetcher?: (input: string, init?: RequestInit) => Promise<Response>): Promise<string>;
/**
 * CSP source hosts required by the configured fonts, so the policy can be
 * auto-extended (Google Fonts need their CSS + static hosts allow-listed).
 */
declare function fontCspSources(fonts?: FontConfig): {
    style: string[];
    font: string[];
};

/**
 * App configuration loader (`wrnexus.config.ts`).
 *
 * The config is optional. It lets an app inject arbitrary `<head>` HTML (ideal
 * for CDN-delivered CSS frameworks like Bootstrap or the Tailwind Play CDN) and
 * customise the global stylesheet pipeline (entry file or a custom processor for
 * Tailwind / PostCSS / Sass).
 */

type Mode = "development" | "production";
interface StyleProcessContext {
    /** Resolved absolute path to the CSS entry, or null if there is none. */
    entryPath: string | null;
    appDir: string;
    appRoot: string;
    mode: Mode;
}
interface StylesConfig {
    /** Path to the CSS entry, relative to the app root. Default: app/styles/global.css */
    entry?: string;
    /**
     * Optional custom processor. Return the final CSS string. Use this to run
     * Tailwind, PostCSS, Sass, etc. When omitted, the built-in Bun CSS bundler is
     * used (which already resolves `@import`, including from node_modules).
     */
    process?: (ctx: StyleProcessContext) => string | Promise<string>;
}
interface MobileConfig {
    enabled?: boolean;
    /** Mobile renderer. `webview` uses Capacitor; `native` scaffolds an Expo/React Native app. */
    mode?: "webview" | "native";
    appId?: string;
    appName?: string;
    serverUrl?: string;
    userAgent?: string;
    layout?: string;
    backgroundColor?: string;
    icon?: string;
    errorTitle?: string;
    errorMessage?: string;
    /** Base URL used by a fully native client for WRNexusJS API and realtime requests. */
    apiUrl?: string;
    /** URL scheme used for native deep links (defaults to a slug of appName). */
    scheme?: string;
    /** Advanced Expo app config fields merged into generated app.config.ts. */
    expo?: Record<string, unknown>;
    /** Advanced CapacitorConfig fields merged into generated capacitor.config.ts. */
    capacitor?: Record<string, unknown>;
}
interface PwaScreenshot {
    src: string;
    sizes: string;
    type?: string;
    formFactor?: "wide" | "narrow";
    label?: string;
}
interface PwaShortcut {
    name: string;
    shortName?: string;
    description?: string;
    url: string;
    icons?: Array<{
        src: string;
        sizes: string;
        type?: string;
        purpose?: string;
    }>;
}
interface PwaConfig {
    enabled?: boolean;
    id?: string;
    name?: string;
    shortName?: string;
    description?: string;
    startUrl?: string;
    scope?: string;
    lang?: string;
    display?: "standalone" | "fullscreen" | "minimal-ui" | "browser";
    orientation?: "any" | "natural" | "landscape" | "landscape-primary" | "landscape-secondary" | "portrait" | "portrait-primary" | "portrait-secondary";
    themeColor?: string;
    backgroundColor?: string;
    icons?: Array<{
        src: string;
        sizes: string;
        type?: string;
        purpose?: string;
    }>;
    categories?: string[];
    screenshots?: PwaScreenshot[];
    shortcuts?: PwaShortcut[];
    /** Disable service-worker registration while keeping the web manifest. */
    serviceWorker?: boolean;
    /** Navigation shown when both the network and requested page cache are unavailable. */
    offlineUrl?: string;
    /** Additional same-origin URLs precached during service-worker installation. */
    cacheUrls?: string[];
    /** Service-worker cache key. Change it to invalidate existing PWA caches. */
    cacheName?: string;
}
interface AppConfig {
    /** Raw HTML appended to every page's `<head>` (e.g. CDN stylesheet links). */
    head?: string | string[];
    /** Global SEO defaults merged with every page's exported `meta`. */
    seo?: SeoConfig;
    /** Framework security headers and optional CORS policy. */
    security?: SecurityConfig;
    styles?: StylesConfig;
    /** Capacitor/native shell defaults and mobile-only page rendering. */
    mobile?: MobileConfig;
    /** Progressive Web App metadata. Enabled by default unless set to false. */
    pwa?: PwaConfig | false;
    /**
     * Fonts. Declare Google Fonts (subsetted + preconnect + `font-display`) and/or
     * self-hosted `@font-face` (with preload), and set `sans`/`mono`/`serif` family
     * stacks. Google Fonts auto-extend the CSP so they load under the default policy.
     */
    fonts?: FontConfig;
    /** Design-token themes (deep-merged over the built-in light/dark). */
    theme?: ThemeConfig;
    /** i18n: default language + supported locales (strings live in app/locales/*.json). */
    i18n?: {
        default?: string;
        locales?: string[];
    };
    /** Default database connection (driver + url); reached with `getDb()`. */
    db?: {
        driver: "sqlite" | "postgres" | "mysql" | "mongo";
        url: string;
    };
    /**
     * File-upload storage. Declare named stores (local dir or S3-compatible),
     * upload with `handleUpload`/`upload` from `@wrnexus/uploader`, and serve
     * files back. Each store is `access: "public" | "private"`.
     */
    storage?: StorageConfig;
    /**
     * Additional named databases, reached with `getDb("<name>")`. Each has its own
     * migrations/queries under `app/db/<name>/`. Connect to as many as you like and
     * read/write to any of them per request.
     *
     *   databases: { analytics: { driver: "postgres", url: "…" } }
     */
    databases?: Record<string, {
        driver: "sqlite" | "postgres" | "mysql" | "mongo";
        url: string;
    }>;
    /**
     * Realtime scaling. When `scale` is true (or `redisUrl` is set), room
     * broadcasts are bridged over Redis pub/sub so they reach clients on **every**
     * app process/instance — realtime that works with multiple running apps.
     */
    realtime?: {
        scale?: boolean;
        redisUrl?: string;
    };
    /** Default server port. */
    port?: number;
    /**
     * Named config profiles (dev, prod, uat, test, …). When a profile is active
     * its overrides are DEEP-MERGED over the base config. Select with
     * `--profile=<name>` or the `WRNEXUS_PROFILE` env var.
     */
    profiles?: Record<string, Partial<Omit<AppConfig, "profiles">>>;
}
/**
 * Resolve the active profile name: explicit argument > `WRNEXUS_PROFILE` env var
 * > a mode-based default ("production" in prod, else "development").
 */
declare function resolveProfile(options?: {
    explicit?: string;
    mode?: Mode;
}): string;
/** Load the raw `wrnexus.config.*` (with the `profiles` map intact), or `{}`. */
declare function loadRawConfig(appRoot: string): Promise<AppConfig>;
/** Load `wrnexus.config.*`, applying the active profile's overrides. */
declare function loadAppConfig(appRoot: string, profile?: string): Promise<AppConfig>;
/**
 * Load the `.env` cascade for a profile into `process.env`, WITHOUT clobbering
 * variables already set in the real environment (which always win). Order, low
 * → high precedence: `.env` < `.env.<profile>` < `.env.local` < `.env.<profile>.local`.
 * Returns the variables it loaded.
 */
declare function loadEnv(appRoot: string, profile: string): Record<string, string>;
/** Flatten a head config into a single HTML string. */
declare function headToString(head?: string | string[]): string;

/**
 * Global stylesheet pipeline.
 *
 * Convention: `app/styles/global.css` is the entry. If it is absent but other
 * `app/styles/*.css` files exist, they are aggregated into one entry. The entry
 * is bundled by Bun's CSS bundler, which resolves `@import` — including from
 * node_modules — so any npm CSS framework (Bootstrap, etc.) works by importing
 * it. A custom `process` hook can replace the bundler for Tailwind/PostCSS/Sass.
 */

/**
 * Resolve the CSS entry for an app.
 *  - `override` (from config.styles.entry) is resolved relative to `appRoot`.
 *  - otherwise prefer `app/styles/global.css`.
 *  - otherwise aggregate all `app/styles/*.css` into a generated entry.
 * Returns null when the app has no styles.
 */
declare function findStyleEntry(appDir: string, appRoot: string, override?: string): string | null;
/**
 * Bundle a CSS entry into a single stylesheet string using Bun's CSS bundler.
 * Resolves `@import` (local and node_modules), handles nesting, minifies in prod.
 */
declare function bundleCss(entryPath: string, mode: Mode): Promise<string>;

/**
 * @wrnexus/styles — global stylesheet pipeline + app config.
 *
 * Works for SSR and CSR: the bundled stylesheet is `<link>`ed into every page's
 * `<head>`, so it styles server-rendered markup and hydrated client islands
 * alike. Use any CSS framework via `@import` in global.css (npm) or via a CDN
 * link in `wrnexus.config.ts`'s `head` field.
 */

/**
 * Produce the final CSS for an entry: run the config's custom processor if one
 * is provided (Tailwind/PostCSS/Sass), otherwise use the built-in Bun bundler.
 *
 * If a custom processor throws (e.g. Tailwind can't resolve `tailwindcss`
 * because deps aren't installed), we DON'T crash every request — we log a clear,
 * actionable message and fall back to best-effort CSS so the app keeps serving.
 */
declare function renderStyles(ctx: StyleProcessContext, styles?: StylesConfig): Promise<string>;

export { type AppConfig, DEFAULT_THEMES, type FontConfig, type FontDisplay, type GoogleFont, type LocalFontFace, type MobileConfig, type Mode, type PwaConfig, type ResolvedTheme, type StyleProcessContext, type StylesConfig, type Mode as StylesMode, THEME_COOKIE, THEME_CSS_HREF, THEME_JS_HREF, type ThemeConfig, type ThemeTokens, bundleCss, findStyleEntry, fontCspSources, headToString, loadAppConfig, loadEnv, loadRawConfig, renderFontHead, renderProductionFontHead, renderStyles, renderThemeCss, renderThemeRuntime, resolveProfile, resolveThemeConfig, resolveThemeName };

Examples

Copy-ready examples taken from this package's published documentation.

Example 1

bun add @wrnexus/styles

Example 2

interface StylesConfig {
  /** CSS entry path relative to the app root. Default: app/styles/global.css */
  entry?: string;
  /** Custom processor — return the final CSS string (Tailwind/PostCSS/Sass). */
  process?: (ctx: StyleProcessContext) => string | Promise<string>;
}

interface StyleProcessContext {
  entryPath: string | null; // resolved absolute CSS entry, or null
  appDir: string;
  appRoot: string;
  mode: Mode; // "development" | "production"
}

Example 3

type ThemeTokens = Record<string, string>;

interface ThemeConfig {
  default?: string; // theme used when no cookie is present
  themes?: Record<string, ThemeTokens>; // deep-merged over built-in light/dark
}

interface ResolvedTheme {
  default: string;
  names: string[];
  themes: Record<string, ThemeTokens>;
}

Example 4

import type { AppConfig } from "@wrnexus/styles";

export default {
  head: [
    '<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@5/dist/css/bootstrap.min.css">',
  ],
  port: 3000,
  db: { driver: "sqlite", url: "app.db" },
  theme: {
    default: "dark",
    themes: {
      light: { "color-primary": "#7c3aed" }, // override one token; rest inherited
      brand: {
        // add a whole new theme
        "color-scheme": "dark",
        "color-bg": "#0a0a0a",
        "color-primary": "#22d3ee",
      },
    },
  },
  styles: {
    entry: "app/styles/main.css",
  },
  profiles: {
    production: {
      db: { driver: "postgres", url: process.env.DATABASE_URL! },
    },
  },
} satisfies AppConfig;