---
title: "Sizing and alignment"
description: "Icons sized in em beside text and in fixed steps when standalone, optically aligned rather than box-aligned, and always separated from their touch target."
url: "https://opsinjs.pensievelabs.org/foundations/iconography/sizing-and-alignment"
source: "https://opsinjs.pensievelabs.org/foundations/iconography/sizing-and-alignment.md"
section: "Foundations"
kind: "foundation"
reviewed: "2026-09-20"
reviewer: "engineering"
aliases: ["icon size", "optical alignment", "icon stroke weight"]
---

> Elements written as `<PascalCase … />` below are opsinjs documentation
> components. Their attributes are the content: the values they render are
> generated from `tokens/*.json` and `registry/catalogue.ts` and are
> published separately at https://opsinjs.pensievelabs.org/r/index.json and under the Reference
> section.
> Nothing is missing from this page. The data simply does not live in
> the prose.

<PageTemplate kind="foundation" />

## Overview [#overview]

Icon sizing looks like a detail and is the difference between a glyph that reads
as part of a sentence and one that reads as a sticker placed near it. Three
things decide it: whether the icon lives in text or on its own, whether it is
aligned to its box or to what the eye sees, and whether its size has anything to
do with the area a finger has to hit.

The last of those is the one with consequences. &#x2A;*An icon's size and its touch
target are unrelated quantities.** A 20-point glyph inside a 44-point control is
correct. A 44-point glyph is a large picture, not a large button.

## How it works [#how-it-works]

**In text, size in `em`.** An icon beside a label, inside a sentence, or in a
list row takes its size from the text it accompanies. It then grows with the
reader's text-size multiplier automatically, which is the whole reason for the
rule. A glyph fixed at 16 pixels beside text that has grown to 21 looks
progressively detached and eventually stops reading as part of the same element.
See [Dynamic Type](../typography/dynamic-type.mdx).

**Standalone, size in fixed steps.** An icon that is not in a text context takes
one of a small number of fixed sizes from the space scale. There are three:
small for inline marks and dense rows, medium for controls and list affordances,
large for a category or status mark that is the visual anchor of a tile. The
glyph inside an icon-only control and a category mark on a tile are both
standalone icons.

**Align optically, not to the box.** An icon's bounding box and its visual centre
are rarely the same, particularly for glyphs with asymmetric ink. An arrow, a
play triangle and anything with a tail are all glyphs of that sort. Aligning
boxes puts the glyph visibly off centre. The correction is small, per glyph, and
it is why the system uses one library: a mixed set means a different correction
for every icon and no way to generalise.

**Align to the text's optical centre, not to its baseline.** A glyph set on the
baseline sits low, because the eye centres a symbol against the body of the
letterforms rather than against the line they stand on. The right alignment for an
icon beside a single line of text is the vertical centre of the x-height and cap
height together.

**Match stroke weight to text weight.** lucide draws with a consistent stroke, and
that stroke reads heavier or lighter depending on the weight of the type beside
it. A glyph against a bold heading and the same glyph against regular body text
need different stroke weights to look like the same object; the mapping is part of
the icon role rather than left to each component.

**Do not scale a glyph far from its design size.** Icons are drawn on a grid, and
a set drawn for a small size renders soft or misaligned when blown up several
times. That is a second reason the standalone sizes are a fixed short list rather
than a free parameter.

## Using it [#using-it]

**Never grow the icon to grow the target.**

<DoDont>
  <DoDont.Do>
    A dismiss control at the 44-point floor with a medium glyph centred inside
    it. The tap area is generous and the glyph is the size a glyph should be.
  </DoDont.Do>

  <DoDont.Dont>
    The same control sized to the glyph, so the target is 24 points, or the glyph
    scaled to 44 so the target is right and the icon looks like a sticker. Size
    the control and the glyph independently.
  </DoDont.Dont>
</DoDont>

**Give an icon a gap, and take it from the space scale.** The distance between a
glyph and its label is a spacing decision, and `hairline` or `tight` is almost
always the answer. See [Spacing scale](../space/spacing-scale.mdx). Too much and
they stop reading as one element; too little and the glyph looks stuck to the
first letter.

**Do not mix sizes in a row.** A list where each row's icon is sized to its
content produces a ragged left edge that the eye reads as disorder, on a screen
where order is doing real work.

**Reserve the space even when the icon is absent.** A list where some rows have a
category glyph and some do not should keep the column, or every label shifts. A
shifting left edge is harder to scan than a slightly empty one.

**Check it at 1.3×.** The `em` sizing means glyphs grow; the fixed sizes mean the
controls containing them may not. An icon-only control in a row of text is where
those two meet, and it is where the layout first breaks.

## Tokens [#tokens]

The three standalone sizes, the `em` ratio used in text contexts, the icon-to-
label gap and the stroke weights come from `tokens/space.json` and
`tokens/type.json`, generated by `scripts/build-tokens.mts`. Iconography owns no
token family of its own, deliberately: an icon size that is not derived from the
type and space scales is an icon size that does not scale with them.

<TokenTable scope="space" />

## Accessibility impact [#accessibility-impact]

* **Sizing in `em` is what makes icons participate in Dynamic Type.** A fixed
  pixel glyph beside scaled text is the most common way an interface looks broken
  specifically for readers who have set a larger text size.
* **Icon size is not target size.** WCAG 2.2 SC 2.5.8 measures the interactive
  area; opsinjs sets a higher floor on
  [Density and touch](../space/density-and-touch.mdx). A small glyph in a large
  target passes; a large glyph in a small target does not, however easy it is to
  see.
* **Thin strokes at small sizes fail non-text contrast** more often than any other
  mark in the system. A meaningful glyph is measured against its background under
  SC 1.4.11, at the size and stroke weight it actually renders at.
* **Optical alignment is a legibility matter, not a polish one.** A glyph that
  sits visibly low beside a label reads as a separate object, which weakens the
  association the icon existed to create.
* **Reserved space keeps scanning cheap** for readers with low vision or reduced
  attention, who rely on a stable left edge to move down a list.

## Related [#related]

* [Density and touch](../space/density-and-touch.mdx) covers the target floor an
  icon sits inside, and why the two measurements are separate.
* [Dynamic Type](../typography/dynamic-type.mdx) says what happens to all of this
  at the reader's own text size.
* [Icon inventory](./icon-inventory.mdx) has the roles these sizes apply to.
