# Energy Icons
[energyicons.com](https://energyicons.com) · [Figma plugin](https://www.figma.com/community/plugin/1687188347733136771/energy-icons)
Open-source icons for the energy transition: 2,063 icons covering solar, wind, hydro, grid, storage, EV charging, heat pumps, industry and engineering, climate, home and travel, health and animals, and the everyday interface glyphs around them. Every icon is drawn at two optical sizes in two weights and published as outlined SVGs. MIT licensed.
```bash
npm install energy-icons
```
```tsx
import { Icon } from "energy-icons/icon";
```
Import one icon when the bundle should stay small: `import { Pylon } from "energy-icons/icons/pylon"`. Sizes below 32 use the 20px master. Sizes from 32 up use the 48px master.
No React? Link the icon font and use classes, as with Phosphor:
```html
```
See [Installation](src/app/docs/installation/page.tsx) and [Usage](src/app/docs/usage/page.tsx) in the site, and `packages/energy-icons/README.md`.
The icons are free. [Support the library on Ko-fi](https://ko-fi.com/energyicons) if you want to support the drawing.
## Contributing
Icon requests, drawing rules, and how to run the checks are in [CONTRIBUTING.md](CONTRIBUTING.md).
This repo also holds the library website: a Next.js app with search, category filters, a size slider, and a detail view where you can copy or download an SVG.
## Run it
Requires Node 20 or later (see `.nvmrc`).
```bash
npm install # also generates the icon registry (postinstall)
npm run dev # http://localhost:3000
```
| Script | What it does |
| --- | --- |
| `npm run dev` | Dev server. Runs `npm run icons` first. |
| `npm run studio` | Sanity Studio for the glossary, at http://localhost:3333. Run it beside `npm run dev`. |
| `npm run build` / `npm start` | Production build and server. `prebuild` runs `npm run icons`. |
| `npm run icons` | Validates `/icons` against the metadata, then regenerates the registry and the Download all zip. |
| `npm run lint` | ESLint. |
| `npm run typecheck` | `tsc --noEmit`, after generating icons and Next route types. |
| `npm test` | Icon system tests, including the byte-identical path check. |
| `npm run check` | Runs lint, typecheck, test and build in order. |
## The two-master scaling rule
Every icon has two optical masters:
| Master | Grid | Drawn with | Used for |
| --- | --- | --- | --- |
| `20.svg` | 20×20 | 1px stroke | 12, 14, 16, 18, 20, 24, 28 |
| `48.svg` | 48×48 | 2px stroke | 32, 40, 48, 64 |
| `20-bold.svg` | 20×20 | 1.25px stroke (Bold) | 12, 14, 16, 18, 20, 24, 28 |
| `48-bold.svg` | 48×48 | 2.5px stroke (Bold) | 32, 40, 48, 64 |
Every icon comes in two weights, **Regular** and **Bold**. Bold is its own pair of hand-built masters (same construction rules, heavier stroke), never a re-weighted Regular, and the same breakpoint picks its 20 or 48 master. Bold is optional per icon: an icon without both Bold files is shown Regular-only and the Bold switch falls back to Regular for it (`npm run icons` lists any such icons).
**Sizes below 32px use the 20 master. Sizes from 32px up use the 48 master.** The chosen master is scaled proportionally with `width` and `height`. The viewBox stays at the master's own grid.
Hard rules: path data is never edited, stroke widths are never changed, and `non-scaling-stroke` is never used. The SVGs exported from Figma are the source of truth. Copy SVG and Download SVG output the source file with only the root `width`/`height` changed. `npm test` checks that the path data is byte-identical for every icon at every size.
### Where the breakpoint lives
`src/config/icons.ts`:
```ts
export const OPTICAL_MASTER_BREAKPOINT = 32;
```
This one constant controls the `` component, the toolbar's master indicator, the detail view, copy and download output, and the size tables in the docs pages. Supported sizes (`SUPPORTED_SIZES`) and the default size (`DEFAULT_ICON_SIZE`) are in the same file.
## Using the component
The website uses an in-repo ``. Published apps use the `energy-icons` package described above.
```tsx
import { Icon } from "@/components/icon";
// decorative (aria-hidden)
// labelled (role="img")
// inherits currentColor
// Bold weight (default "regular")
```
- `name` is typed as a union of every known slug, so a typo is a type error.
- The icon renders as inline SVG with `fill="currentColor"`, so it takes on the surrounding text colour.
- The master is picked automatically from `size`. The default size is 32.
## Adding a new icon
1. **Draw both masters in Figma.** Use a 20×20 frame with a 1px stroke and a 48×48 frame with a 2px stroke. Outline the strokes, flatten, and use a single fill. Export each frame as SVG with the full frame as the viewBox.
2. **Add the files** as `icons//20.svg` and `icons//48.svg`, plus the Bold masters `20-bold.svg` and `48-bold.svg`. The slug is kebab-case, for example `heat-network`. The viewBox must be `0 0 20 20` / `0 0 48 48` and fills should be `currentColor`. Don't hand-edit the paths.
3. **Add one metadata entry** to `src/data/icons.ts`. Its position in the list sets its position in the grid:
```ts
{
slug: "heat-network",
name: "Heat network",
category: "heat-buildings", // any id from src/data/categories.ts
keywords: ["district heating", "heat network", "pipes"],
},
```
4. **Run `npm run dev`, or `npm run icons` on its own.** The icon now appears in the grid, search, its category filter, the detail view, `` (with type checking) and the Download all zip.
If a metadata entry is missing a file, or a folder in `/icons` has no metadata, `npm run icons` exits with an error that names the problem. Because `dev`, `build`, `typecheck` and `test` all run it first, a broken icon can't slip through. The check also rejects the wrong viewBox, `non-scaling-stroke`, scripts and embedded styles or images. It warns about live strokes, hard-coded fills and `id`s.
Categories with no icons are hidden, and a category's filter appears automatically once it has an icon.
## Architecture
```
icons//20.svg, 48.svg Source masters, exactly as exported from Figma
icons//20-bold.svg, 48-bold.svg Bold masters (optional per icon)
scripts/generate-icons.ts Validation, registry generation and zip (npm run icons)
src/
config/icons.ts Breakpoint, supported sizes, master selection
config/site.ts Site name and GitHub / Figma / sponsor link slots
data/categories.ts Site categories and the Figma category mapping
data/icons.ts Typed metadata (slug, name, category, keywords) and the IconName union
generated/icon-registry.ts GENERATED: Regular SVG markup keyed by slug and master (gitignored)
generated/icon-registry-bold.ts GENERATED: Bold SVG markup, same shape (gitignored)
generated/icon-registry-*.ts GENERATED: one browser chunk per weight and master (gitignored)
lib/icons/ SVG helpers (withSize), getIconSvg, search and filter
components/icon.tsx (and to render a specific master)
components/library/ Toolbar, grid, detail dialog, state provider
components/layout/ Sidebar, theme toggle, menu button
components/docs/ Docs page primitives
components/hero/ Animated hero (WebGL field, frames) and the floating library stage
app/ Routes: /, /category/[category], /docs/*, /glossary, /roadmap
studio/ Sanity Studio for the glossary (npm run studio)
src/sanity/ Sanity client, GROQ queries, live content
design/energy-hero-settings.json Hero field settings exported from Toolcraft
public/downloads/energy-icons.zip GENERATED at build time for Download all (gitignored)
public/figma/v1/*.json GENERATED icon data the Figma plugin loads from energyicons.com (gitignored)
packages/figma-plugin/ The Figma plugin (npm run figma), see its README
tests/ node:test suites run with tsx
```
- **Registry.** The generator reads each SVG and stores the source file once, keyed by slug and master. viewBox and inner markup are derived from that string. `` renders the inner markup inside an `