Skip to content
Protocore Design Systemv1.6.9

/// getting-started

Styling

PDS is styled with plain CSS custom properties and data-attributes — not a utility framework. There are no class soups to memorise and no build-time purge to configure. You theme with variables and vary with attributes.

The idiom

Every component is a .pds-Name class whose look is driven by two things: the token layer it reads (--pds-* semantic variables) and the data-attributes it carries for variants. Sizes, tones, and variants are attributes, never class permutations:

rendered markup
<button class="pds-Button" data-variant="primary" data-size="md">
  Deploy service
</button>

You never write these attributes by hand — you pass props (variant, size, tone) and the component maps them to attributes. The point is that the styling surface is small, declarative, and identical across every component.

Why not utilities?
Utility frameworks put styling decisions at the call site, which is exactly where drift enters a design system. PDS keeps those decisions inside the component and exposes only the props that are meant to vary. Less rope, fewer ways to go off-brand.

Extending safely

Two escape hatches let you adapt a component without forking it. Reach for them in this order.

1. Retune tokens on a scope

Redefine a semantic variable on a wrapper and everything inside follows. This is the preferred way to brand a region — it stays inside the system’s contrast and geometry rules.

scoped token override
.settings-panel {
  --pds-accent: #2a6fdb;   /* accent for this panel only */
}

2. Pass a className

Every DOM-rendering component merges your className after its own pds-* classes, and spreads ...rest onto the root element. Because your class comes last in the list — and CSS specificity between two single classes is a tie broken by source order — a rule of equal specificity from a later-loaded stylesheet wins:

className passthrough
<Button className="cta">Start</Button>
// renders: <button class="pds-Button cta" data-variant="primary" ...>

/* your stylesheet, imported after @protocore/pds/styles.css */
.cta {
  letter-spacing: 0.2em;   /* wins on source order */
}
Import order decides the tie
Passthrough only wins if your stylesheet loads after @protocore/pds/styles.css. Import the PDS stylesheet first (see Installation). Avoid !important — if you need it, you are usually fighting a rule you should be setting as a token instead.

What never to override

A few properties are load-bearing for the brand. Overriding them does not extend the system — it breaks it. Keep your hands off these:

Never overrideWhy
border-radius on controlsSharp corners are the brand. Rounded radius tokens exist but never apply to buttons, inputs, chips, or badges.
box-shadow on controlsStructure is drawn with hairlines. The one sanctioned shadow (--pds-shadow-overlay) is for floating layers — dialogs, popovers, toasts — only.
The --pds-p-* primitivesPrivate layer-1 values. They are not API and can change between releases — always style through the semantic --pds-* layer.
Accent as a fill or statusThe accent is a signal, not decoration. See Colors.

The three token layers

Knowing which layer you are reaching for keeps overrides safe. PDS tokens form a one-directional stack:

LayerPrefixUse it?
1 · Primitives--pds-p-*Private. Never reference directly.
2 · Semantic--pds-color-*, --pds-space-*, --pds-text-*Yes — this is the styling API you override.
3 · Component--pds-control-*, --pds-badge-h, --pds-hairlineDerived; retune with care, density-aware.

The full token reference lives under Foundations.