Icons
Summary
41 Lucide-sourced glyphs shipped as CSS mask images, colored by
currentColor: an element's text color becomes the
icon color, no font and no inline SVG. Each glyph is also a
root-level custom property
(--soma-icon-<name>) so component CSS can use
glyphs without markup (dropdown chevrons, sort indicators).
The set is curated in src/icons/icons.config.mjs;
npm run icons:build regenerates.
When to use
| Pattern | Use it for | Accessibility consequence |
|---|---|---|
| Inline with text | Buttons, menu items, messages: the icon reinforces a visible label and picks up its color. | Nothing extra: the visible text is the accessible name; the empty icon span contributes nothing. |
| Icon-only control | Toolbar actions, dismiss buttons — the icon is the label. | The control MUST get an aria-label (or
hidden-text fallback, below); otherwise it announces
as an unnamed button. |
| Decorative | A visual hint that adds no information next to visible text. | Already silent: an empty <span> has
no accessible name and no role; don't add a label
that would duplicate the text. |
Examples
HTML
An inline icon picks up currentColor from its parent:
<span class="soma-icon soma-icon-settings"></span>
Inside a button with a text label, nothing extra is needed:
<button class="soma-button">
<span class="soma-icon soma-icon-plus"></span>
Add item
</button>
CSS-only usage in your own component styles (no markup span):
.my-thing::before {
mask-image: var(--soma-icon-chevron-down);
}
Labelling icon-only controls
An icon-only control has no text, so it has no accessible name.
Give it one with aria-label:
<button class="soma-button soma-button-subtle" aria-label="Edit">
<span class="soma-icon soma-icon-pencil"></span>
</button>
Alternatively, text inside the icon span is visually hidden (clipped by the mask box) but still read by screen readers; use it as a fallback label where an attribute is awkward, e.g. markup generated from data:
<span class="soma-icon soma-icon-mail">Email</span>
The glyph grid above uses exactly this idiom — each cell's icon span contains the glyph name as hidden text.
CSS classes
| Class | Effect |
|---|---|
.soma-icon | Base 16px glyph box; combines with a per-glyph class. |
.soma-icon-<name> | Selects the glyph (names below each cell). |
.soma-icon-small | Explicit 16px, identical to the base; exists so markup can state the size. |
.soma-icon-large | 32px. |
JavaScript
None — icons are CSS-only. Color comes from
currentColor, so styling is just a matter of setting
color on the parent (or the icon span itself).
Adding a new icon
The set is a curated list in
src/icons/icons.config.mjs, each entry mapping a
Soma glyph name to a Lucide source SVG. Append an entry:
// src/icons/icons.config.mjs
export default [
{ name: 'apps', source: 'lucide', from: 'grid-3x3' },
// …
// add your new icon:
{ name: 'heart', source: 'lucide', from: 'heart' },
];
Then npm run icons:build regenerates
src/css/_icons.generated.scss (the
dev and build scripts also run it
automatically, and the output is committed so the repo builds
without re-running it). Your glyph is now
.soma-icon-heart and
--soma-icon-heart.
Two entry options: strokeWidth overrides Lucide's
default stroke of 2 — the close glyph ships at
strokeWidth: 3.10 because at dismiss-button sizes
the stock ✕ reads lighter than every other glyph. And
source: 'inline' embeds a hand-drawn or third-party
glyph as a raw SVG string:
{ name: 'close', source: 'lucide', from: 'x', strokeWidth: 3.10 },
{ name: 'brand', source: 'inline', svg: '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">…</svg>' },
If the inline geometry isn't yours, add the attribution to
NOTICE, the same rule the Lucide set itself
follows.
One-off icons without the build script
Consuming apps never need the build script: each glyph class
only sets the --soma-icon-mask custom property, and
the base class does all the rendering. A one-off icon is plain
CSS:
.icon-heart {
--soma-icon-mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' …%3E…%3C/svg%3E");
}
<span class="soma-icon icon-heart"></span>