Persona

Icons

Three size builds of the PDS icon library that are not the same set as each other.

Updated Sep 22, 2026

PDS ships 218 icons as React components in @persona/icons, in three size builds. They are line drawings on a square grid, partly derived from Feather, which is where the stroke weight and the corner treatment come from.

The older @persona-id/design-tokens package still exports icons and still resolves, and it is not the same set: it has twelve social marks — Facebook, GitHub, LinkedIn, Yelp and the rest — that @persona/icons dropped, and it is missing six that @persona/icons added. @persona/design-system imports @persona/icons, so that is the set the components you see are drawn from.

An icon in this library is markup and nothing else: an svg with paths filled by currentColor. It carries no name, no role, no size token, and no color prop. Everything below is a consequence of that.

The three sizes are three separately drawn sets

Section titled “The three sizes are three separately drawn sets”

12px has 117, 16px has 214, and 24px has 213 icons. 113 names exist in all three. Each build draws on its own viewBox, so AddIdIcon at 24 is a different set of paths from AddIdIcon at 16, redrawn to sit on a 24-unit grid rather than scaled up from a 16-unit one.

Import the build that matches the size you need. The size prop exists and defaults to the build’s own dimension, but setting it to 24 on a 16px import scales a 16-unit drawing, which is the case the three builds were made to avoid — half-pixel strokes and a stroke weight that no longer matches the icons beside it.

The three builds are not the same set with three renderings. The differences are small, specific, and will not fail at build time:

  • ArrowExpondIcon, ChevronDoubleDownIcon, ChevronDoubleUpIcon and RotatetCwIcon exist only at 12px. Two of those names are misspellings that shipped — the 16px and 24px builds have ArrowExpandIcon and RotateCwIcon, and at 12px you type the typo or you get nothing.
  • BiometricIcon exists at 16px only, so the icon you found in the default import is not there at either other size.
  • LinkIcon and UnlinkIcon in the 12px build declare viewBox="0 0 16 16". At the build’s default size of 12 they draw a 16-unit design into a 12px box, so both render at three quarters the size of every icon next to them.
  • CirculeMinusFillIcon is a second, slightly different drawing of CircleMinusFillIcon. It is one unit too wide at 16px and at 24px — 17-by-16 and 26-by-24 — and correctly square at 12px. StrikethroughIcon is one unit too tall in both builds it appears in, 16-by-17 and 24-by-25, and does not exist at 12px.

Every icon names itself in a browser tooltip

Section titled “Every icon names itself in a browser tooltip”

The title prop defaults to the component’s own name and is spread onto the svg, so an unconfigured CaretDownIcon renders title="CaretDownIcon" and browsers show that string on hover. Three anatomy figures on this site strip exactly that attribute out of PDS’s rendered markup before drawing it.

A title attribute is also not an accessible name. Nothing in an icon sets aria-hidden, a role, or aria-label; the accessible name of an icon-only control comes from the control, and Accessibility is where that is set out. What the icon owes you is the title: pass a real one, or pass an empty string to stop the component name leaking.

One related trap in the same spread: className is applied as pds-icon and then overwritten by anything you pass, because the props spread comes after it. Pass a class and you drop pds-icon with it.

An icon is the color of the text it sits in

Section titled “An icon is the color of the text it sits in”

The paths are fill="currentColor". There is no color prop doing anything special — a color you pass lands on the svg as CSS and the fill inherits it. So an icon inside a label is already the label’s color, and an icon you have to color separately is usually one that should not have been separated from its text.

This also means an icon’s contrast is its text’s contrast. There is nothing icon-specific to check and nothing in PDS that checks it; Color shows what the palette computes and Accessibility says what that does and does not certify.

Five filled icons have no outline counterpart

Section titled “Five filled icons have no outline counterpart”

AiFillIcon, CircleFillIcon, CircleWarningFillIcon, WarningFillIcon and CirculeMinusFillIcon ship filled with no outline in the library; the other seven filled icons have both. So “keep filled and outlined consistent within a set” is not a rule the library can always honor — twelve concepts out of 218 have a filled form, five of those have only the filled form, and a group containing one of the five cannot be uniform. Choose per group rather than per product.

An icon alone works only where the action is already named

Section titled “An icon alone works only where the action is already named”

An icon replaces a label when the reader already knows what the control does — a close affordance on a dialog, a caret on a disclosure, a chevron in a row that opens something. In each of those the meaning is carried by position and by what else is on screen, not by the drawing.

Everywhere else the icon goes beside the label rather than instead of it, because an icon that has to be learned charges the reader a guess every time until they have learned it, and nothing on the screen tells them whether they guessed right. Two different icons for one concept is the same cost paid twice: it teaches the reader that the drawing is not the part to read.

The library is the only source. There is no documented route in this repo for adding an icon to it or for approving a one-off, so a drawing that is not among the 218 is currently a decision somebody makes without a record.

Was this page helpful?