Utility blocks
Blocks that surface project-level state rather than tokens themselves.
<Diagnostics />
<Diagnostics />
Project-load diagnostics (parser errors, resolver warnings, disabled-axis validation) as a collapsible list. Green OK badge when clean; auto-expands with a severity summary when warnings or errors are present.
Props: caption?: string overrides the summary heading.
The Diagnostic shape
Every loadProject() call returns a diagnostics: Diagnostic[] list. The block renders it directly.
interface Diagnostic {
severity: 'error' | 'warn' | 'info';
group: string;
message: string;
filename?: string;
line?: number;
}
group identifies the subsystem that produced the diagnostic:
parser: Terrazzo's token-file parser (malformed JSON, invalid$value, wrong$type, …).resolver: resolver-file issues (missing$ref, unknown modifier, context with no files).swatchbook/presets: preset validation (unknown axis key, invalid context value).- Plugin-originated groups: depends on your setup.
Severities
error: something is structurally broken. A token may be missing from the graph, an alias may be unresolved. The block shows these with a red badge.warn: something is unusual or probably-a-mistake but the project loaded. Example: a preset with an unknown axis key is dropped and produces a warn.info: diagnostic for the curious. Rare.
Reading common diagnostics
parser: Could not resolve alias "{color.palette.blue.500}": the alias target doesn't exist in the merged graph. Check for a typo in the path, or a missing token file that wasn't included intokensglobs.parser: Invalid token value for $type "color": the$valuedoesn't parse as the declared$type. Usually a string missing a unit or a color in the wrong format.swatchbook/presets: Preset "X" has unknown axis key "Y": a preset references an axis that doesn't exist on the project. Either the axis name was renamed or the preset predates the resolver change.
Failing CI on errors
The block surfaces diagnostics without failing the Storybook build. For CI that should fail on error-severity diagnostics, check them in a pre-build script:
import { loadProject } from '@unpunnyfuns/swatchbook-core';
import config from './swatchbook.config.ts';
const project = await loadProject(config, process.cwd());
const errors = project.diagnostics.filter((d) => d.severity === 'error');
if (errors.length > 0) {
console.error(errors);
process.exit(1);
}
Color formatting
The color-rendering blocks (<ColorPalette>, <ColorTable>, <TokenDetail>, sub-color fields in <ShadowPreview> / <BorderPreview> / <GradientPalette>) all route through one helper. The helper plus its types are exported from @unpunnyfuns/swatchbook-blocks for custom blocks that need to stringify a color the same way.
import {
formatColor,
COLOR_FORMATS,
type ColorFormat,
type FormatColorResult,
type NormalizedColor,
} from '@unpunnyfuns/swatchbook-blocks';
type ColorFormat = 'hex' | 'rgb' | 'hsl' | 'oklch' | 'raw';
const COLOR_FORMATS: readonly ColorFormat[];
function formatColor(
value: unknown,
format: ColorFormat,
fallback?: string,
): FormatColorResult;
interface FormatColorResult {
/** Display string — e.g. "rgb(59 132 246)" or "#3b82f6". */
value: string;
/** True when the requested format can't losslessly represent the color. */
outOfGamut: boolean;
}
hexfalls back to space-separatedrgb()for out-of-gamut colors and marks themoutOfGamut: true; blocks render a ⚠ glyph beside the value.oklchis wide-gamut and never marksoutOfGamut.rawreturns a compact JSON of the normalized Terrazzo shape, useful for DTCG authors who want to see exactly what the parser stored.fallbackdefaults to'—'; pass a custom string when integrating with surfaces that need a different placeholder.
COLOR_FORMATS is the runtime list used to validate stored user choices; reach for it when you need to enumerate the formats (toolbar pills, validation) without duplicating the union.
NormalizedColor is the shape formatColor accepts: a structural copy of Terrazzo's ColorValueNormalized (colorSpace, components / channels, alpha, optional hex). Use it when typing custom block code that produces or consumes the same payload.