The generated icon set — how icons are sized, themed, named, and added, and every glyph in one place.
Propel ships 934 icons across 18 categories, published as their own entry
point. Every icon is a React component, sized to the text it sits beside, and colored by the
surrounding text color rather than a value baked into the file.
import { SearchFilled } from "@makeplane/propel/icons";<SearchFilled />;
Import icons individually, never as a namespace. Each icon is its own module, so a build only
includes the ones you name — import * as icons defeats that and pulls in all 934.
Icons carry no "use client" boundary. They are static SVG with no state, hooks, or event
handlers, so they render on the server inside a React Server Component without shipping any
JavaScript to the browser.
Sizing and color
An icon renders at 1em and inherits currentColor, so it takes both from its context rather than
from props. Set the size with a font-size utility and the color with a text utility on the icon or
anything above it — there is no size or color prop to reach for. The exception is
StateGroupIcon: it takes a size prop that sets width and height in pixels.
{/* 1rem, inherits the text color */}<SearchFilled />{/* Scaled and tinted by the surrounding text */}<span className="text-body-lg-regular text-danger-strong"><SearchFilled /></span>
Every icon is aria-hidden by default, which is correct for the common case — an icon beside a
label that already names the action. When an icon is the only content of a control, the control
needs its own accessible name; put it on the control, not the icon.
Icons follow the active theme because they paint in currentColor rather than a fixed value. Two
deliberate exceptions, and one refinement:
Category
Paint
Why
everything else
currentColor
Follows the surrounding text color, so one glyph works on every theme.
other-products
brand palette
A third-party logo is wrong in any color but its own — Slack's four hues, Claude's terracotta, YouTube's red.
priority-{level}
currentColor + var(--priority-unlit)
A level is lit bars over dimmed ones. The lit bars follow the text color; the dimmed bars paint in the unlit token so they hold their weight in both themes.
The priority glyphs — PriorityUrgent, PriorityHigh, PriorityMedium, PriorityLow,
PriorityNone — are the one two-tone family. Set the level color on the icon with its text-priority-*
token and the unlit segment takes care of itself:
StateGroupIcon — exported from the same @makeplane/propel/icons entry — draws a work item’s
state together with its progress. Pass stateGroup: backlog, unstarted, started,
completed, or cancelled. started and unstarted render percentage as continuous progress: a
solid outer ring advances clockwise from twelve while fifteen radial ticks disappear behind it.
Values at or below 1 are fractions; values above 1 are percentages. It defaults to 100. size
is xs 10px, sm 12px (the default), md 14px, lg 16px, xl 18px.
UnstartedGroupIcon and StartedGroupIcon are those two glyphs on their own. The static sets —
cycles, initiatives, milestones, modules, and work items — sit in the gallery below.
import { StateGroupIcon } from "@makeplane/propel/icons";<StateGroupIcon stateGroup="started" percentage={37} size="lg" />
The brand exemption is narrower than the category. It applies only to marks that genuinely carry a
palette: a logo exported as a single flat neutral is a monochrome wordmark, not a palette, so it
themes like any other icon. GitHub, X, Intercom, ChatGPT and Docker all fall in that group —
hardcoding their near-black would leave them at roughly 1:1 contrast against the dark theme’s own
near-black background, which is to say invisible.
Outside other-products, theming is enforced rather than encouraged: an icon left carrying a
concrete color after generation fails the build instead of shipping unthemed. Plane’s own product
marks under sub-brands get no exemption — they are themed like everything else.
Naming
Export names are derived mechanically from the source filename, so the name is predictable from the
glyph rather than something to look up.
Source
Export
Rule
search-filled.svg
SearchFilled
kebab-case becomes PascalCase.
search-outline.svg
SearchOutline
The fill state is always last. An icon with a state ends in Filled or Outline, with no exceptions.
share-alt-filled.svg
ShareAltFilled
alt marks a second variant of the same concept, and still leaves the state last.
404-outline.svg
Icon404Outline
A digit-leading name gets an Icon prefix, since an identifier cannot start with a number.
formatting-and-editors/table-filled.svg
TableEditorFilled
When two categories claim one filename with different art, the later one takes a short category qualifier — before the state, never after it.
Adding an icon
src/icons/ is generated. Never hand-edit it: check:icons regenerates the tree and diffs it
against what is committed, so an edit made by hand fails CI on the next run.
# 1. Drop the SVG into packages/propel/icons/<category>/ — directly in the# category folder; a stray or nested file fails the build rather than# being silently skipped.# 2. Regenerate.pnpm --filter @makeplane/propel gen:icons# 3. Commit the SVG and the generated output together.
Generation writes to a scratch directory and swaps it in only on success, so a run that fails the
color policy or hits a name collision leaves the committed tree untouched.
Two things the generator reports without failing, because it cannot tell a deliberate choice from a
source mistake: an icon painting a rendering white (a Figma knockout, which halos on the dark
theme), and groups of icons whose markup is byte-identical — usually a -filled and -outline
pair cut from the same glyph. Run ICONS_REPORT_DUPES=1 pnpm gen:icons to list the latter.
The set
Search by export name, or filter by category. Click any icon to copy its import.
Showing 934 of 934 — click an icon to copy its import.