ACSS 4 Iconography
Icons in ACSS 4 are driven by an attribute, not a class. Put data-icon on an svg, i or a and the framework sizes it, colours it and centres it. Everything below is live markup — inspect any icon to see the whole API. Line icons are Iconoir; the duotone set is Phosphor.
The API
One attribute, one scale
[data-icon] sets display, colour, flex-shrink and box-sizing. The .icon--* classes only set --icon-size and --icon-padding, so an icon with no size class renders at the 32px default.
.icon--xs.icon--s.icon--m.icon--l.icon--xlEvery size class has an attribute twin. .icon--l and data-icon-size="l" are the same rule, so you can size an icon without touching its class list:
data-icon-size="s"data-icon-size="l"Caveat found while building this page: --icon-size-2xl is not emitted on this install, so .icon--2xl falls through to the hard 24px fallback and renders smaller than .icon--m. The scale is usable from xs to xl.
Sets
Two families, one contract
Any SVG works — the framework never touches the artwork. These line icons paint with currentColor; the duotone set paints its solid layer with currentColor and its back layer with --icon-duo-color, so one class pairs them as primary over base.
Iconoir — line
1.5px strokes, 24px grid. Colour comes from --icon-color through currentColor.
Phosphor — duotone
Two layers on a 256px grid. .icon-duo sets --icon-color to primary and --icon-duo-color to base.
Lists
Icon lists
.icon-list — or the data-icon-list attribute — turns a plain ul into a flex column and re-sizes every icon inside it to 1em, so the bullets track the text rather than a fixed pixel size. .icon--yes and .icon--no are the only two colour shortcuts ACSS ships.
What the framework handles
- Sizing, colour and alignment from one attribute
- Automatic 1em scaling inside any icon list
- Card-aware defaults with no extra markup
- Hover states through –icon-color-hover
What stays yours
- The icon set itself — bring any SVG
- The artwork, including stroke weight
- Layout of the block around the list
- Which items read as yes and which as no
Boxes
Icon boxes
Inside a card the framework takes over: .card :where([data-icon]) re-points --icon-size at --card-icon-size, which this install sets to 68px. That is illustration scale, not interface scale — so .card--icon re-points it once at the m step and every icon in the grid follows. Add .icon--boxed and the glyph sits in a tinted chip instead of floating on the card.
Chips
Boxed icons
.icon--boxed adds padding, a border, a radius and a background around the glyph, all of them variables. Out of the box it lands on the neutral scale; four variable overrides move it onto the brand scale. .icon--plain is the inverse — it zeroes the box again.
Defaults, untouched:
The same class with --icon-color, --icon-background, --icon-border-color and --icon-radius re-pointed at brand tokens:
Callouts
Callouts with icons
A callout is a card that runs horizontally. .callout carries five declarations — it flips --card-flex-direction, retints --card-background, drops the ring and adds one accent edge that a modifier can re-point. Everything else is the card framework, so it inherits padding, radius, gap and hover for free.
Consolidate, do not duplicate
Because .callout shares more than 80% of its styling with .card, it is a modifier rather than a component. Five lines, not forty.
Recolour through the card
Inside a card .icon--no is dead: .card :where([data-icon]) re-declares --icon-color and wins on source order. The card-level variable is the way in, so .callout--danger sets --card-icon-color instead.
Testimonials
Testimonials with icons
ACSS switches its blockquote framework off inside anything whose class contains -card, so figure.card.testimonial-card hands the box straight back to the card framework — no override, no fight. The only icon work left is the star row: .rating sets --card-icon-size once on the parent instead of a size class on each of the five stars.
The icon layer never fights the artwork. We dropped in a set we already licensed and the sizing, colour and hover states came along for free.
Switching the whole product from 48 to 40 pixel icons was one variable. That is the part that keeps paying us back.
One contract, any surface
On a dark band the icons need one class, not a new component: .icon-banner re-points --icon-color and --icon-duo-color, and every descendant follows. The row itself is .icon-row with the centred modifier.