This guide explains how to add new Material icons from Figma and how the icon system works.
Use this checklist when you need to add a new icon:
-
Get approval and SVG from Design team
- Confirm the icon is approved for the design system
- Get the icon name from the Figma layer (should be PascalCase, e.g.,
AppointmentBooking) - Export SVG from Figma at 24x24px
-
Check and adjust colors
- Most icons: Replace fill colors with
currentColorso they inherit CSS color - Brand-colored icons (social media, specific branded icons): Keep original Figma colors (e.g.,
#011D4B,#FFC62C)
- Most icons: Replace fill colors with
-
Add the SVG file
# Save to packages/toolkit/assets/icons/ # Example: packages/toolkit/assets/icons/AppointmentBooking.svg
-
Update the manifest
- Open
packages/toolkit/assets/icons/manifest.json - Add entry with
name(PascalCase),category, anddefaultSize:
{ "name": "AppointmentBooking", "category": "Action", "defaultSize": 24 }- Categories:
DataValidation,Action,Arrows,Graphical,Stepper,Socials
- Open
-
Rebuild the sprite
pnpm --filter @ourfuturehealth/toolkit exec node scripts/build-icon-sprite.js -
Test and verify
- Build toolkit:
pnpm --filter @ourfuturehealth/toolkit build - Run docs locally:
pnpm dev:site - Check the icon appears at
/design-system/styles/icons - Use in your component:
{{ icon({ "name": "AppointmentBooking", "size": 24 }) }}
- Build toolkit:
- Engineers adding new icons from Figma
- Engineers consuming toolkit icons in components/templates
- Maintainers understanding the icon system architecture
Overview of how the icon system works:
- Source files and metadata live in
packages/toolkit/assets/icons/. - A build script converts those SVGs into one sprite file:
packages/toolkit/assets/icons/icon-sprite.svg. - Toolkit components render icons by name via the icon macro and
<use href="...#ofh-icon-name">. - The docs site reads the same manifest and renders the icon gallery from it.
packages/toolkit/assets/icons/manifest.json: source of truth for icon names, categories, and default sizes.packages/toolkit/assets/icons/*.svg: one SVG file per icon.packages/toolkit/assets/icons/icon-sprite.svg: generated sprite used at runtime.
The sprite is generated by packages/toolkit/scripts/build-icon-sprite.js:
- Reads all SVG files from
assets/icons - Sorts them by filename for deterministic output
- Converts each file into a
<symbol id="ofh-icon-<name>"> - Writes
assets/icons/icon-sprite.svg
Run it directly from repo root:
pnpm --filter @ourfuturehealth/toolkit exec node scripts/build-icon-sprite.jsIt also runs automatically in toolkit build via gulp:
packages/toolkit/gulpfile.jsbuildMaterialIconSpritegulp buildtask sequence
- One shared source for icon path data
- Consistent API (
name+size) across components - Smaller repeated markup in templates (
<use>instead of full path content each time) - Easy to update icon internals without touching every component template
Toolkit components use the shared icon macro:
The macro renders:
- An
<svg>element with.ofh-iconclasses - A
<use href="/assets/icons/icon-sprite.svg#ofh-icon-<name>">
Example usage:
{% from 'icon/macro.njk' import icon %}
{{ icon({ "name": "Search", "size": 24, "title": "Search" }) }}Core icon styles are in packages/toolkit/core/styles/_icons-material.scss and imported in packages/toolkit/core/all.scss.
Current status: the React package does not yet have a shared icon component.
When React icon support is added, this doc should be updated with the final approach. Recommended baseline:
- Keep icon names aligned with toolkit
manifest.json - Reuse the same icon semantics (
name,size, decorative vs titled) - Decide implementation explicitly:
- Sprite-based
<use>approach (consistency with toolkit/docs), or - Build-time inlined SVG components
- Sprite-based
Related package: packages/react-components/
The docs site does not hardcode the icon list. It reads toolkit manifest data:
- Data source:
packages/site/views/_data/materialIcons.js - Gallery page:
packages/site/views/design-system/styles/icons/index.njk
Flow:
materialIcons.jsloadstoolkit/assets/icons/manifest.json- It sorts and groups icons by category
- The icons page loops through those groups and calls the shared icon macro
The sprite is copied into docs output by Eleventy:
The sprite builder includes all *.svg files in packages/toolkit/assets/icons/, but the docs gallery reads from manifest.json.
Solution: Ensure the icon has an entry in manifest.json with the exact same name as the SVG filename (without .svg).
New categories need to be added to the category order list.
Solution: Add the category name to packages/site/views/_data/materialIcons.js in the categoryOrder array.
Most icons should use currentColor to inherit CSS colors, but some icons need specific brand colors.
Icons that need brand colors preserved:
- Social media icons (LinkedIn, X, Facebook, Youtube, Instagram, Tiktok) and their hover states
ArrowCircleRightColour(uses#011D4Band#FFC62C)
Solution: For brand-colored icons, keep the original Figma fill colors. For all other icons, replace fill colors with currentColor.
Check the SVG viewBox and ensure it's exported at the correct size.
Solution: Icons should be exported from Figma at 24x24px with viewBox="0 0 24 24". The sprite builder preserves the viewBox from individual SVG files.