ReferenceTokens

Sizing

The control-sizing contract — a single set of --wr-control-* tokens that every form control reads, so button, input, select and friends line up pixel-for-pixel at each size and track density together. Plus the radius scale that frames controls, containers and overlays.

The control-sizing contract

Three sizes — sm (22px), md (30px, the default) and lg (36px). A control's height is never hard-coded. It is the sum of its line-height, twice its vertical padding and twice its 1px border. Keeping the parts as separate tokens means padding-y can stay density-aware while line-height holds the text box steady.

:root {
  /* sm → 22px · md → 30px · lg → 36px */
  --wr-control-padding-y-sm: 0.125rem;  /* 2px  → 16 + 2×2 + 2×1 = 22 */
  --wr-control-padding-y-md: 0.25rem;   /* 4px  → 20 + 2×4 + 2×1 = 30 */
  --wr-control-padding-y-lg: 0.3125rem; /* 5px  → 24 + 2×5 + 2×1 = 36 */

  --wr-control-padding-x-sm: 0.5rem;    /* 8px  */
  --wr-control-padding-x-md: 0.75rem;   /* 12px */
  --wr-control-padding-x-lg: 1rem;      /* 16px */

  --wr-control-line-height-sm: 1rem;    /* 16px */
  --wr-control-line-height-md: 1.25rem; /* 20px */
  --wr-control-line-height-lg: 1.5rem;  /* 24px */

  --wr-control-font-size-sm: var(--wr-text-xs);   /* 12px */
  --wr-control-font-size-md: var(--wr-text-sm);   /* 14px */
  --wr-control-font-size-lg: var(--wr-text-base); /* 16px */

  --wr-control-radius-sm: 5px;
  --wr-control-radius-md: 6px;
  --wr-control-radius-lg: 7px;
}

The size ladder

Each box below is a bare <div> styled only with the contract tokens — no fixed heights. The rendered height equals line-height + 2×padding-y + 2×1px border, i.e. 22 / 30 / 36px.

sm · 22px
md · 30px
lg · 36px

How a control derives its height

A control points its own --wr-<name>-* knobs at the contract, then renders padding and line-height from them. The sm / lg modifiers simply re-point the same knobs at the matching tier — there is no separate height rule to keep in sync. Because only padding-y is multiplied by --wr-density-y, switching density keeps the whole catalogue aligned.

/* A control never hard-codes its height. It sums the three contract
   parts, and multiplies padding-y by the density factor so the whole
   catalogue tracks density together. From the button styles: */
.wr-btn {
  --wr-btn-padding-y: var(--wr-control-padding-y-md);
  --wr-btn-line-height: var(--wr-control-line-height-md);
  --wr-btn-font-size: var(--wr-control-font-size-md);
  --wr-btn-radius: var(--wr-control-radius-md);

  line-height: var(--wr-btn-line-height);
  font-size: var(--wr-btn-font-size);
  /* height = line-height + 2×padding-y(×density) + 2×1px border */
  padding: calc(var(--wr-btn-padding-y) * var(--wr-density-y, 1))
    calc(var(--wr-btn-padding-x) * var(--wr-density-x, 1));
}

/* The 'sm' / 'lg' modifiers just re-point the same knobs at the
   matching tier of the contract — so button, input and select line
   up pixel-for-pixel in a row at every size. */
.wr-btn--sm {
  --wr-btn-padding-y: var(--wr-control-padding-y-sm);
  --wr-btn-line-height: var(--wr-control-line-height-sm);
  --wr-btn-font-size: var(--wr-control-font-size-sm);
  --wr-btn-radius: var(--wr-control-radius-sm);
}

The contract tokens

NameDescriptionTypeDefault
--wr-control-padding-y-{sm,md,lg}Vertical padding per size. Multiplied by --wr-density-y at consume time, so it stays the only density-aware part of the height.length2px · 4px · 5px
--wr-control-padding-x-{sm,md,lg}Horizontal padding per size. Multiplied by --wr-density-x at consume time.length8px · 12px · 16px
--wr-control-line-height-{sm,md,lg}The text box height — the fixed core the padding is added around.length16px · 20px · 24px
--wr-control-font-size-{sm,md,lg}Label size per tier — 12 / 14 / 16px. Aliases the type scale so it stays in step with typography.length`--wr-text-xs` · `--wr-text-sm` · `--wr-text-base`
--wr-control-radius-{sm,md,lg}Corner radius per control size. Climbs gently so larger controls read a touch rounder.length5px · 6px · 7px

The shared size API

Every form control takes the same size input — sm, md (default) or lg. One mental model across the catalogue: pick a size once and button, input and select agree on it.

<!-- One size scale everywhere — sm · md (default) · lg. The attribute
     name differs on the input directive: it is `size`, so it cannot clash
     with the native `<input size>`. -->
<button wr-btn size="sm">Small</button>
<button wr-btn size="md">Medium</button>
<button wr-btn size="lg">Large</button>

<input wrInput size="sm" placeholder="Small" />
<wr-select size="lg" placeholder="Large">…</wr-select>
import { input } from '@angular/core';

// Each control re-exports its own size alias, but they share one shape.
export type WrButtonSize = 'sm' | 'md' | 'lg';
export type WrInputSize = 'sm' | 'md' | 'lg';
export type WrSelectSize = 'sm' | 'md' | 'lg';

// In the component the input defaults to 'md' and only emits a class
// when it differs — so the base styles cover the common case.
readonly size = input<WrButtonSize>('md');
// host: const size = this.size();
//       if (size !== 'md') parts.push(`wr-btn--${size}`);

The radius scale

Two families. Controls use the tight --wr-control-radius-{sm,md,lg} ramp (5 / 6 / 7px) so they read crisp at small sizes. Containers and overlays use --wr-border-radius-*, anchored on a 10px base for panels — cards, dropdowns, dialogs, toasts — with sm (6px) for nested rows, lg (16px) for large surfaces and pill (50rem) for fully-rounded ends. Circles (avatars, radio dots, FABs) use border-radius: 50% directly — there is no token for them.

:root {
  /* Controls — the squircle radii that sit on buttons / inputs / selects. */
  --wr-control-radius-sm: 5px;
  --wr-control-radius-md: 6px;
  --wr-control-radius-lg: 7px;

  /* Containers & overlays — cards, dropdowns, dialogs, toasts… */
  --wr-border-radius-sm: 0.375rem;  /* 6px  — nested items (e.g. dropdown rows) */
  --wr-border-radius-base: 0.625rem; /* 10px — the default panel radius */
  --wr-border-radius-lg: 1rem;       /* 16px — large surfaces */
  --wr-border-radius-pill: 50rem;    /* fully-rounded ends (pill buttons, tags) */
}

/* Circles are not a token — avatars, radio dots and FABs use 50% directly. */
.wr-avatar { border-radius: 50%; }

Disabled

Part of the control contract too — a control that will not respond has one fade strength, not seven.

NameDescriptionTypeDefault
--wr-disabled-opacityHow far a control fades when it will not respond. One number, because the catalog had seven — 0.6 in eighteen places, then 0.5, 0.7, 0.55, 0.4, 0.35 and 0.65 across 29 entry points, with date-picker and calendar each disagreeing with themselves three ways inside one file. Override it in a component block, not per declaration: wr-calendar does, because a day from the adjacent month is still navigable at 0.55, so a disabled day has to be fainter than that or the two swap meanings.0.6

The radius tokens

NameDescriptionTypeDefault
--wr-control-radius-smSmall control corners (size="sm" button / input / select).length5px
--wr-control-radius-mdDefault control corners.length6px
--wr-control-radius-lgLarge control corners.length7px
--wr-border-radius-smNested surfaces inside a panel — e.g. dropdown / context-menu rows.length0.375rem (6px)
--wr-border-radius-baseThe default container / overlay radius — cards, dropdowns, dialogs, toasts, popovers.length0.625rem (10px)
--wr-border-radius-lgLarge surfaces that want a softer frame.length1rem (16px)
--wr-border-radius-pillFully-rounded ends — pill buttons, tags, the pill shape modifier.length50rem

See also