Files
skills/docs/ui/react/react-control-surface/src/skin/surface-skin-provider.md
2026-05-29 08:20:56 +01:00

3.1 KiB

SurfaceSkinProvider

(no submodules)

Purpose

<SurfaceSkinProvider> is the single React boundary that activates a skin for every atomic control inside its subtree. It writes the resolved palette of CSS custom properties (--cs-*) onto a wrapping <div>'s inline style attribute; every atomic control reads its colours through var(--cs-…) declarations, so changing skin is purely a CSS-variable swap with no React-side dispatch.

This is the only path through which a skin influences rendering. The schema spec is explicit on this point: there is no JS-level skin registry, no React.useContext consumed by controls, and no themed component variants. Keeping skin enforcement on the CSS side enables a single physical surface to host two side-by-side skinned subtrees without forking the React tree.

Features

  • Closed SurfaceSkinVarName enumeration of canonical variables.
  • Frozen DEFAULT_SURFACE_SKIN_VARS palette.
  • Pure buildSkinStyle(vars) helper for unit testing.
  • Caller-supplied className appended to the default rcs-skin class.
  • data-skin-id attribute on the wrapper for integration-test selectors.

Usage: Basic

import { SurfaceSkinProvider } from "@suaveplan/react-control-surface/skin";

export function App(): React.ReactNode {
    return (
        <SurfaceSkinProvider id="dark">
            <Surface />
        </SurfaceSkinProvider>
    );
}

Usage: Advanced

import { SurfaceSkinProvider } from "@suaveplan/react-control-surface/skin";

const orangeKnobOverride = {
    "--cs-knob-track-color": "#ff8800",
    "--cs-knob-indicator-color": "#ffe0b2",
} as const;

export function NkiSurface(): React.ReactNode {
    return (
        <SurfaceSkinProvider
            id="native-instruments"
            vars={orangeKnobOverride}
            className="device-frame"
        >
            <Surface />
        </SurfaceSkinProvider>
    );
}

API Reference

  • SurfaceSkinProvider(props): React.ReactNode
    • id: string — written to data-skin-id on the wrapper.
    • vars?: SurfaceSkinVars — partial override map merged on top of the defaults; entries whose value is undefined keep the default.
    • className?: string — appended to the "rcs-skin" base class.
    • children?: React.ReactNode
  • buildSkinStyle(vars?: SurfaceSkinVars): CSSProperties — pure merge helper.
  • DEFAULT_SURFACE_SKIN_VARS, SURFACE_SKIN_VAR_NAMES — re-exported defaults and the canonical CSS variable name list.

Implementation Notes

The provider intentionally renders a <div> rather than a fragment because CSS variables only cascade through real DOM ancestors. Inline styles are used (instead of injecting a stylesheet) so server-side rendering remains deterministic and consumers see exactly the colours they configured without load-order pitfalls. Overrides are processed as a string-keyed loop so arbitrary --cs-foo properties — useful for one-off device-specific themes — pass straight through to the DOM without requiring an addition to the closed SurfaceSkinVarName union.