Skip to content

Composed icons lose their size when a glyph binds to an icon component #379

Description

@nathanacurtis

Problem

Composed slot content now resolves primitive layers to the design system's own components (ADR-074). A glyph layer becomes the library's icon component, carrying the concepts the binding maps — colour and name.

It does not carry size, and for an icon component that sizes itself from a prop, the result renders wrong.

In a Card's default example, the badge's icon is authored at 16×16. It emits as:

<Icon appearance="On positive" name="add" style={{ width: '<sizing token>', height: '16px' }} />

The icon component has two independent size channels:

  • Its root box, which the inline style shrinks to 16px
  • Its inner glyph element, sized only by a data-size attribute driven by a size prop

size is never passed, so it stays at its default and the inner glyph renders 24×24 inside a 16×16 root. The root clips its content, so the icon comes back cropped.

Before the binding existed, a composed glyph emitted a self-contained masked span carrying its own dimensions inline, and rendered correctly at any size.

Why it is not simply an oversight

ADR-075 deliberately excluded size from the glyph concept set. The reasoning was that sizing should travel as sizing — the spec records width and height, and every platform already understands those — rather than becoming a per-library enum that every other design system then has to model.

That reasoning holds right up until the component ignores its own box, which is what an icon component with a size prop does.

The information needed is already present and unambiguous: the composed width and the component's own extra-small variant resolve to the same sizing token. Nothing has to be guessed; it has to be matched.

Options

  1. Map size as a glyph concept, matched on token identity. Compare the composed dimension against the sizing tokens the bound component's own variants use, and pass the variant that matches. Renders correctly, needs no hand-maintained table, and stays one concept. It reopens ADR-075's decision to keep size off glyphs and Decision 4's rule that conventions map prop names and never values.
  2. Map size with a declared value table in conventions. Explicit and simple to read, but it is the unbounded, drift-prone mapping ADR-075 Decision 4 rejected: it grows with the design system and silently rots when a token is renamed.
  3. Do not bind a glyph that carries explicit sizing. Fall back to the self-contained masked span, which is correct at any size. Principled — never bind when the bound component cannot express what the composition asks for — but it unbinds most composed icons, because authored examples nearly always set an explicit size.
  4. Accept it. Composed icons render at the component's default size unless a design happens to use that default. Cheapest, and visibly wrong wherever it is not.

The same question will apply to any concept where a component overrides its own box from a prop. Icons are simply where it shows first.

Acceptance criteria

  • A composed icon authored at a non-default size renders at that size
  • Whatever rule is chosen is stated in the ADR, not only in code
  • A composed glyph with no explicit size still renders at the component's default
  • The decision is expressed once and applies to both React and Web Components output

Workspace

Found in a design system library workspace while reviewing composed slot content in Storybook, after enabling container, text and glyph primitive bindings for the first time.

Impacted code

  • packages/react-from-specs/src/Spec/primitives.ts — the glyph concept set and its attribute emission
  • packages/webcomponents-from-specs/src/Spec/primitives.ts — the same, for custom elements
  • packages/react-from-specs/src/Spec/composition.ts and its Web Components counterpart — where composed slot content resolves a primitive
  • adr/075-primitive-style-prop-mapping.md — the concept set and the no-value-mapping decision

Text fixtures

  • A card whose slot content includes a badge containing an icon at a non-default size — the reported case, and the smallest reproduction
  • A component whose composed icon uses the design system's default size, confirming the fallback still renders
  • A composed icon inside a nested instance, checking the rule survives more than one level of composition

Notes

Related to ADR-074 (primitives resolve at emit time), ADR-075 (the concept set and value mapping), and ADR-076 (the root constraint on container bindings).


Implementation details are tracked internally.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

react-from-specsTransformer from specs into React codewebcomponents-from-specsTransformer from specs into web components code

Type

Fields

Priority

Moderate

Projects

  • Status
    Backlog

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions