CEM D5 Stroke & Separation — Boundaries, Dividers, and Focus Indicators

Status: Proposed (canonical CEM spec)
Last updated: December 19, 2025

Taxonomy placement: D5. Stroke & Separation (part of the 7-dimensional CEM token framework)

Companion specs:


Contents

  1. What D5 controls
  2. Design goals
  3. Minimal stroke basis
  4. Semantic endpoints (product-facing contract)
  5. Focus, selected, and target indicators
  6. Dividers and separators
  7. Inter-dimension coupling
  8. Accessibility and forced-colors requirements
  9. Implementation guidance (CSS recipes)
  10. Framework mapping guidance (Angular Material, MUI)
  11. Canonical token summary
  12. Governance and versioning
  13. References

1. What D5 controls

D5 defines the geometry of separation:

D5 does not define:


2. Design goals

2.1 Consumer semantics first

Tokens must express what the stroke means to the consumer, not how it is implemented:

2.2 Bounded complexity

Stroke systems can explode into per-component and per-state width tokens. CEM avoids that by defining:

2.3 No layout shift on interaction

Changing border width on focus is a common source of layout shift (e.g., label jumps in outlined text fields). Focus and selection indicators should be drawn using outline or box-shadow, or on a pseudo-element, rather than changing border-box geometry.

2.4 Accessibility as a hard constraint

D5 must support modern focus-indicator requirements (including minimum visible area/thickness and sufficient contrast in high-contrast themes). See §8.


3. Minimal stroke basis

CEM D5 uses a 4-step stroke basis that covers almost all UI needs without a large token surface.

3.1 Stroke basis tokens

These tokens are foundational. Semantic endpoints map to them.

:root {
  /* Basis: keep small and stable */
  --cem-stroke-none:     0px;  /* no stroke */
  --cem-stroke-hair:     1px;  /* hairline separators, subtle boundaries */
  --cem-stroke-standard: 2px;  /* focus/selection indicators; high legibility */
  --cem-stroke-strong:   3px;  /* high-emphasis boundaries; contrast themes */
}

Notes

cem-stroke-basis
Token Value Description tier
--cem-stroke-none 0px No stroke required
--cem-stroke-hair 1px Hairline separators / subtle boundaries required
--cem-stroke-standard 2px Focus / selection / target indicator thickness required
--cem-stroke-strong 3px High-emphasis boundaries / contrast themes required

3.2 Density coupling (D2 interaction)

Stroke thickness is allowed to vary by density mode as a non-breaking adjustment, as long as semantics stay intact:


4. Semantic endpoints (product-facing contract)

These are the canonical semantic endpoints that components consume.

4.1 Endpoints

:root {
  /* Canonical boundaries */
  --cem-stroke-boundary:          var(--cem-stroke-hair);

  /* Canonical separators */
  --cem-stroke-divider:           var(--cem-stroke-hair);

  /* Canonical indicators */
  --cem-stroke-focus:             var(--cem-stroke-standard);
  --cem-stroke-selected:          var(--cem-stroke-standard);
  --cem-stroke-target:            var(--cem-stroke-standard);

  /* Placement */
  --cem-stroke-indicator-offset:  2px; /* distance outside the edge, not thickness */

  /* Optional convenience endpoints (aliases, not required by contract) */
  --cem-stroke-grid:              var(--cem-stroke-divider);        /* tables / data grids */
  --cem-stroke-boundary-strong:   var(--cem-stroke-standard);       /* dialogs / sheets when needed */
}
cem-stroke-semantic
Token Value Description tier
--cem-stroke-boundary var(--cem-stroke-hair) Default control container edge (text fields, chips, cards) required
--cem-stroke-boundary-strong var(--cem-stroke-standard) Strong boundary when elevation/shadow cannot carry separation recommended
--cem-stroke-divider var(--cem-stroke-hair) Sibling separators (list rows, table rows, sections) required
--cem-stroke-grid var(--cem-stroke-divider) Tables / data-grid divider alias recommended
--cem-stroke-focus var(--cem-stroke-standard) Keyboard focus indicator thickness required
--cem-stroke-selected var(--cem-stroke-standard) Selection indicator thickness required
--cem-stroke-target var(--cem-stroke-standard) Deep-link target indicator thickness required
--cem-stroke-indicator-offset 2px External ring/outline offset (distance, not thickness) required

4.2 Semantics (normative)

Optional convenience endpoints (aliases):


4.3 Component mapping matrix (informative)

The D5 contract intentionally avoids per-component thickness tokens (e.g., --cem-input-outline). Instead:

Rule of thumb: color is D0, thickness is D5, corner curvature is D3, spacing/insets is D1, density-mode overrides are D2.

Component family Primary D5 endpoint(s) Typical stroke usage Notes
Dividers (horizontal/vertical) --cem-stroke-divider 1px hairline between siblings Prefer inset patterns for lists; increase strength in dense or low-contrast contexts.
List item separators --cem-stroke-divider (or --cem-stroke-grid) Row separators Treat dense “data-list” layouts as a grid for readability.
Table borders / grid lines --cem-stroke-grid Cell/row separators Use grid to distinguish “structured separators” from generic dividers; same thickness by default.
Text field outline (outlined pattern) --cem-stroke-boundary Control container edge Focus indication should be a ring (--cem-stroke-focus) rather than mutating border width.
Text field underline (filled pattern) --cem-stroke-boundary or --cem-stroke-divider Baseline boundary Underline is a boundary of the control, not a sibling divider; map to divider only when visually used as “row separation”.
Outlined button border --cem-stroke-boundary Variant boundary Use boundary-strong only for “hard separation” themes (e.g., low-elevation UIs).
Card / tile border --cem-stroke-boundary (often --cem-stroke-none) Optional edge Prefer D4 elevation or D0 surface contrast; use boundary only when needed for scannability.
Checkbox / radio container border --cem-stroke-boundary Control boundary The checkmark/dot is not a stroke token concern; it is icon geometry (D3) + color (D0).
Switch track outline (if any) --cem-stroke-boundary Track boundary Track height is D2/D1, not D5; D5 only provides line weight if outlined.
Chip / badge border (bordered variant) --cem-stroke-boundary Subtle outline In dense chips, consider mapping boundarystandard via D2 coupling if needed for separation.
Tabs / segmented control indicator --cem-stroke-selected Active underline/bar thickness Avoid using divider for active indicators; indicators communicate state, not separation.
Focus ring (all controls) --cem-stroke-focus + --cem-stroke-indicator-offset External ring/outline Must satisfy WCAG 2.2 focus appearance (§8.1).
Target highlight (deep link / “jump to”) --cem-stroke-target Temporary outline/ring Distinct from focus: target is navigational, focus is interactive.
Menus / dropdowns / popovers (edge) --cem-stroke-boundary (often none) Optional border Prefer D4 shadow separation; use boundary/boundary-strong in forced-colors or shadow-suppressed contexts.
Progress/slider tracks (usually none) / --cem-stroke-boundary Outline only if required Track thickness (e.g., 8px) is a dimension; treat that as D2/D1, not D5.

5. Focus, selected, and target indicators

CEM uses an outline-driven indicator model for states that must remain visible across backgrounds:

5.1 Zebra outline system (existing in theme-data.xhtml)

The current theme implementation uses a zebra ring composed of stacked outside strokes using box-shadow and zebra color variables:

In normal themes, a 3-stripe ring is used; in contrast themes, a 4-stripe ring is used (intent + focus + selected + target).

Ownership split (R-D5-1 resolved): --cem-zebra-strip-size and --cem-zebra-color-{0..3} are owned by D0 (cem-colors) and emitted by cem-colors.html because they ship with full theme-mode coverage (.cem-theme-{light,dark,contrast-light,contrast-dark,native}). D5 owns:

D5 ring recipes reference D0-owned zebra tokens via var(); D5 does NOT redeclare them.

5.2 Canonical zebra tokens (D5 contract)

D5 treats these as canonical indicator-pattern tokens:

:root {
  --cem-zebra-strip-size: 2px;      /* thickness per stripe */
  --cem-zebra-angle:      45deg;    /* if stripes are rendered as gradients */
  --cem-zebra-color-0:    Canvas;       /* intent / base stripe (contrast themes) */
  --cem-zebra-color-1:    CanvasText;   /* focus stripe */
  --cem-zebra-color-2:    SelectedItem; /* selected stripe */
  --cem-zebra-color-3:    SelectedItem; /* target stripe (or themed alternative) */
}

--cem-zebra-strip-size and --cem-zebra-color-{0..3} are owned by D0 (cem-colors) and emitted by cem-colors.html. D5 only declares --cem-zebra-angle and the ring composition recipes below.

cem-stroke-zebra-pattern
Token Value Description tier
--cem-zebra-angle 45deg Stripe angle for gradient-mode zebra recommended
/* 3-stripe ring: focus/selected/target (normal themes) */
--cem-ring-zebra-3:
  0 0 0 calc(1 * var(--cem-zebra-strip-size)) var(--cem-zebra-color-1),
  0 0 0 calc(2 * var(--cem-zebra-strip-size)) var(--cem-zebra-color-2),
  0 0 0 calc(3 * var(--cem-zebra-strip-size)) var(--cem-zebra-color-3);

/* 4-stripe ring: intent + focus + selected + target (contrast themes) */
--cem-ring-zebra-4:
  0 0 0 calc(1 * var(--cem-zebra-strip-size)) var(--cem-zebra-color-0),
  0 0 0 calc(2 * var(--cem-zebra-strip-size)) var(--cem-zebra-color-1),
  0 0 0 calc(3 * var(--cem-zebra-strip-size)) var(--cem-zebra-color-2),
  0 0 0 calc(4 * var(--cem-zebra-strip-size)) var(--cem-zebra-color-3);

Why box-shadow?

cem-stroke-rings
Token Value Description tier
--cem-ring-zebra-3 0 0 0 calc(1 * var(--cem-zebra-strip-size)) var(--cem-zebra-color-1), 0 0 0 calc(2 * var(--cem-zebra-strip-size)) var(--cem-zebra-color-2), 0 0 0 calc(3 * var(--cem-zebra-strip-size)) var(--cem-zebra-color-3) 3-stripe focus/selected/target ring recommended
--cem-ring-zebra-4 0 0 0 calc(1 * var(--cem-zebra-strip-size)) var(--cem-zebra-color-0), 0 0 0 calc(2 * var(--cem-zebra-strip-size)) var(--cem-zebra-color-1), 0 0 0 calc(3 * var(--cem-zebra-strip-size)) var(--cem-zebra-color-2), 0 0 0 calc(4 * var(--cem-zebra-strip-size)) var(--cem-zebra-color-3) 4-stripe ring (contrast themes) recommended
cem-stroke-rings-forced
Token Value
--cem-ring-zebra-3 0 0 0 var(--cem-stroke-focus) Highlight
--cem-ring-zebra-4 0 0 0 var(--cem-stroke-focus) Highlight

cem-stroke-rings-forced carries forced-colors fallback values for the rings (Principle P4). Generator-only — no new tokens. The same ring names are redeclared inside @media (forced-colors: active) :root { … }.

5.4 Focus heuristics

Prefer :focus-visible semantics. If the platform needs a polyfilled focus-ring behavior, align with heuristics similar to Material Web’s focus ring utilities (see references).


6. Dividers and separators

6.1 Divider role types

CEM recognizes three common divider roles (same thickness basis, different usage):

  1. Section divider: separates groups (higher salience)
  2. Row divider: list/table row hairlines
  3. Gridline: dense tabular separation

All three should normally map to --cem-stroke-divider (hairline). Promote to --cem-stroke-boundary-strong when:

6.2 Inset patterns (non-token guidance)

Avoid creating many inset tokens. Use a single inset rule:

Spacing and inset should use D1 tokens.


7. Inter-dimension coupling

7.1 D3 shape coupling

Indicator rings should follow component rounding:

border-radius: var(--cem-bend-control);
box-shadow: var(--cem-ring-zebra-3);

If the ring is outside, it may require a slightly larger radius:

border-radius: calc(var(--cem-bend-control) + var(--cem-stroke-indicator-offset));

7.2 D1 spacing coupling

Where indicators sit outside the edge, ensure surrounding layout provides enough breathing room. Prefer:

Treat --cem-coupling-guard-min as the default clearance budget between adjacent operable zones. If you increase any of the following, you must also increase surrounding D1 spacing and/or D2 guard/halo (or accept overlap):

See the D2 compatibility rule in cem-coupling §4.1.1.

7.3 D4 elevation coupling

Elevation and stroke are substitutes:


8. Accessibility and forced-colors requirements

8.1 Focus appearance (WCAG 2.2)

The focus indicator must be large/visible enough to be reliably perceived. A robust default is a 2px perimeter (or equivalent area) around the focused component, which D5’s --cem-stroke-focus supports.

8.2 Forced colors / high contrast

In forced-colors: active contexts:

Suggested baseline:

@media (forced-colors: active) {
  .cem-focusable:focus-visible {
    outline: var(--cem-stroke-focus) solid CanvasText;
    outline-offset: var(--cem-stroke-indicator-offset);
    box-shadow: none;
  }
}

9. Implementation guidance (CSS recipes)

9.1 Prefer rings over border-width mutation

Avoid

/* Causes layout shift */
.control:focus { border-width: 2px; }

Prefer

.control:focus-visible {
  outline: var(--cem-stroke-focus) solid currentColor;
  outline-offset: var(--cem-stroke-indicator-offset);
}

or (for rounded geometry / zebra):

.control:focus-visible {
  box-shadow: var(--cem-ring-zebra-3);
}

9.2 Layering order for multiple indicators

If multiple indicators can apply, compose in a stable order:

  1. focus
  2. selected
  3. target

In zebra, this is naturally expressed as concentric stripes.

9.3 Action components (existing pattern)

Current implementation uses:

.action { box-shadow: var(--cem-action-box-shadow); }
.action:hover { --cem-action-box-shadow: var(--cem-action-box-shadow-hover); }
.action:active { --cem-action-box-shadow: var(--cem-action-box-shadow-active); }

In contrast themes, --cem-action-box-shadow becomes the zebra ring.


10. Framework mapping guidance (Angular Material, MUI)

This section is non-normative. It documents how to map framework tokens/variables into the CEM D5 semantic endpoints.

10.1 Angular Material (MDC-based) mapping

Common MDC variables (examples seen in Angular Material customization workflows):

Angular Material also exposes higher-level variables in some setups, e.g.:

Guidance: map these variables from CEM tokens; do not treat them as canonical tokens in CEM.

10.2 MUI mapping

MUI System

MUI Joy UI Joy exposes per-component CSS variables for focus ring geometry (examples):


11. Canonical token summary

Token Category Required Meaning
--cem-stroke-none Basis No stroke
--cem-stroke-hair Basis 1px hairline
--cem-stroke-standard Basis 2px standard indicator
--cem-stroke-strong Basis 3px strong separation
--cem-stroke-boundary Semantic Default component boundary
--cem-stroke-boundary-strong Semantic Optional Strong boundary for cases where elevation/shadow cannot carry separation
--cem-stroke-divider Semantic Default separator/divider
--cem-stroke-grid Semantic Optional Divider alias for structured content (tables/grids)
--cem-stroke-focus Semantic Focus-visible indicator width
--cem-stroke-selected Semantic Selection indicator width
--cem-stroke-target Semantic Target indicator width
--cem-stroke-indicator-offset Placement Ring/outline offset distance
--cem-zebra-strip-size Pattern Optional Stripe thickness for zebra indicators
--cem-zebra-angle Pattern Optional Stripe angle for gradient zebra
--cem-zebra-color-0..3 Pattern Optional Concentric zebra stripe colors (intent/focus/selected/target)
--cem-action-box-shadow Adapter hook Optional Existing action indicator/shadow hook (implementation detail)

12. Governance and versioning

12.1 What counts as breaking

Treat as major (breaking) if you:

12.2 What is non-breaking

Treat as minor/patch if you:


13. Token manifest index

Source table Section Description
cem-stroke-basis §3.1 Stroke basis: --cem-stroke-{none,hair,standard,strong}
cem-stroke-semantic §4.1 Semantic endpoints: --cem-stroke-{boundary,boundary-strong,divider,grid,focus,selected,target,indicator-offset}
cem-stroke-zebra-pattern §5.2 --cem-zebra-angle (gradient-mode geometry)
cem-stroke-rings §5.3 Ring composition recipes: --cem-ring-zebra-3, --cem-ring-zebra-4
cem-stroke-rings-forced §5.3 Forced-colors fallback values for the rings (generator-only; no new tokens)

Generator derivation rules:


14. References