Icons

Icons

View as markdown

Each icon is one drawing, painted as stroke, twotone, duotone, solid or shaded.

Variants

All from the one drawing. An icon is a list of named parts, each a closed outline. Filling them gives the solid variants, stroking them gives the outlined ones. A correction to the drawing lands in every variant.

stroke
twotone
duotone
solid
shaded
16
20
24
32
48
currentColor

TypeScript

import { StyledIcon } from "@oscarkalid/ui";
<StyledIcon name="hammer" />
<StyledIcon name="hammer" variant="shaded" size={32} />

One icon so far, kept exactly as drawn and in the box it was exported in. Stroke weight scales with that box, and a drawing exported at 51 across and one exported at 24 come out the same thickness.

Usage

One component with a name and a variant. Both are typed, so a wrong one is a build error.

TypeScript

import { StyledIcon } from "@oscarkalid/ui";
<StyledIcon name="hammer" />
<StyledIcon name="hammer" variant="solid" className="text-danger" />

Size and weight

The default size is 1em, so an icon in a line of text matches that text. Give it a number to set its own size.

16
20
24
32
48
weight 2
weight 5

TypeScript

<StyledIcon name="hammer" /> {/* follows the text */}
<StyledIcon name="hammer" size={32} />
<StyledIcon name="hammer" weight={5} /> {/* thicker outline */}

Adding an icon

  1. 1Draw it, then outline the strokesEvery part has to be a closed shape. A live stroke can be drawn but not filled, and the solid variants would come out empty. In Figma that is Object, Outline Stroke.
  2. 2Split it into parts and pick the primary onePrimary is the body of the thing, secondary is what it is attached to: the head of a hammer against its handle, the screen of a monitor against its stand. If you could keep only one shape, that is the primary.
  3. 3Export the frame as SVGAny size. Do not refit it onto a 24 grid. The box travels with the drawing and is normalised at render time.
  4. 4Paste it inOne entry in ARTWORK: the viewBox from the export, and one path per part with its role. Every variant comes from that.

packages/ui/src/icons/styled.tsx

export const ARTWORK: Record<string, IconArtwork> = {
hammer: {
viewBox: "0 0 51 46",
parts: [
{ role: "primary", d: "M21.36 0.04C27.05..." }, // the head
{ role: "secondary", d: "M17.23 19.88C17.35..." }, // the handle
],
},
};

Strokes are drawn inside the silhouette, so parts keep the gaps they were given. Outline weight is scaled from the viewBox, so a drawing exported at 51 across and one exported at 24 come out the same thickness.

The set lives in its own directory with nothing importing out of it, ready to move to icons.oscarkalid.com.


Last updated: 12 August 2026