Skip to content

Advanced guide

This guide covers the design-token auditing surface and tuning the classifier with a custom configuration. For options, the result schema, and the fetch policy, see the usage guide.

Design-token auditing

Beyond the palette, analyze reports what a site's CSS declares versus what it actually renders — useful for auditing a design system you own. Both surfaces live on each theme's ThemePalette:

  • palette.tokens — the declared design tokens (CSS custom properties) with their resolved color and inferred semantic role (e.g. --accent-500 read as brand_accent). Opt-in: pass include_tokens=True to analyze (otherwise the field is None; () means tokens were requested but no usable color tokens were found — none declared, or every declaration filtered as non-color or ignore-classified).
  • palette.divergence — discrepancies between intent and usage, keyed by UsageRole: declared but unused (only high-intent tokens — ones classified by an explicit name rule or relational pattern; unused shades of a numbered color scale, alias followers, and fallbacks deliberately do not fire) and prominent rendered colors that are used but undeclared.
result = await analyze(url, include_tokens=True)
palette = result.themes[Theme.LIGHT]
for token in palette.tokens or ():
    print(token.name, token.color.hex, token.semantic_role)
for item in palette.divergence:
    print(item.role, item.note, item.color.hex)  # e.g. "declared '--brand' unused in render"

Each divergence item carries the affected UsageRole, the Color, and a human-readable note.

Custom tuning

palette_config.yaml ships bundled with the package and is loaded automatically. It is the single source of truth for:

  • the token vocabulary — CSS custom-property names → semantic roles → usage-role priors (how a token's color is expected to be used, over the 8 roles: page / surface / banner / cta / action / text / link / border);
  • the component-classifier weights — how rendered elements are scored into headers, cards, CTAs, and so on. Besides the tag/class/geometry/interactivity rule lists, this includes two presence feature families keyed on structural facts about an element: border_presence (votes applied to any element that genuinely paints a border — the harvester width-gates the border color) and text_presence (votes applied to any non-clickable element with direct text content, so plain <p>/<span> typography is measured even when no semantic rule matches). Each is a single {votes: {component: weight}} mapping; the bundled YAML keeps a short calibration note next to each weight, and the full worked derivations live on the How it works page.

The weights are calibrated starting points, not ground truth. To tune them, copy the bundled file, edit your copy, and pass its path as config_path= to analyze (or load it with load_config). The loader is strict about dispatch names: geometry/interactivity when: predicates and suppressor keys must name predicates the classifier actually implements — an unknown name fails validation at load time rather than becoming a knob that silently never fires. To inspect the defaults programmatically:

from colorsense import load_default_config

config = load_default_config()

config_path= tunes the token vocabulary and the component classifier. The measurement-side scoring constants are documented in-code, not part of the YAML: the usage view's pruning threshold, the role→component collapse, log-damped vote-mass prominence, and the color-index prominence blend in palette/usage.py (MIN_PROBABILITY_SHARE, MIN_EXEMPT_VOTE_MASS, COMPONENT_TYPES_BY_USAGE_ROLE, PROMINENCE_AREA_WEIGHT), the per-channel perceptual join radii in palette/inventory.py (MAX_BG_MATCH_DELTA_E, MAX_TEXT_BORDER_MATCH_DELTA_E, MAX_CLUSTER_MERGE_DELTA_E), and the declared/measured token-match radii in palette/reconcile.py (MAX_TOKEN_MERGE_DELTA_E, MAX_MEASURED_MATCH_DELTA_E).