@bradleyhodges/sfsymbols-react
Companion package for using Apple's SF Symbols icons from @bradleyhodges/sfsymbols in React/Next.js.
SF Symbols is a library of over 7,000 symbols that are designed to integrate seamlessly with San Francisco, the system font for Apple platforms.
Available Icons • 🚀 Getting Started • Install • Usage • Optimising for Performance • Style Entries • Props • VS Code Extension • Issues • Pull Requests
[!CAUTION] SF Symbols is licensed by Apple for use only on Apple platforms. Use of this package, or any of the other @bradleyhodges/sfsymbols-* packages, outside of Apple platforms is NOT permitted.
Available Icons
@bradleyhodges/sfsymbols contains 8,111 icons, which includes 609 supplementary brand icons. All 7,502 of the Regular weight icons from the San Francisco font are included. Other weights and variable icon directives are not included in this package, but the icon weights can be made heavier by applying a stroke to the vector paths.
For simplicity, I've created a basic icon browser for this package, which you can access here: bradleyhodges-sfsymbols.vercel.app
![]()
🚀 Getting Started
This package contains the React components and metadata helpers that allow you to use these icons in your React/Next.js project. The actual icons and icon data for SF Symbols 8 come from the sfsymbols package, which you'll need to install as well.
Install the Packages
You will need to install the following packages to use the icons in your project:
@bradleyhodges/sfsymbols– Icon catalogue package (contains the actual icons).@bradleyhodges/sfsymbols-react– React component wrapper for the using the icons in React/Next.js.
pnpm add @bradleyhodges/sfsymbols @bradleyhodges/sfsymbols-react
The component wrapper renders web DOM <svg> elements. React Native applications need a native SVG renderer instead. This library supports React 18 and 19.
Usage
It's dead simple to get going. Once you've installed the above packages, you can simply import and use them:
import SFIcon from "@bradleyhodges/sfsymbols-react";
import { sfArrowUpCircleFill, sfCheckmark, sfCrossVialFill } from "@bradleyhodges/sfsymbols";
function MyComponent() {
return (
<div>
<SFIcon icon={sfArrowUpCircleFill} />
<SFIcon icon={sfCheckmark} weight={2} />
<SFIcon icon={sfCrossVialFill} size={24} className="text-red-500" />
</div>
);
}
Use the icon browser to find icons you want to use.
Optimising for performance
The default and SFIcon exports are the same memoized, ref-forwarding component. For an opt-in direct icon import, use the exact sf* leaf path. This lets a bundler avoid parsing the catalogue's large root barrel:
import { SFIcon } from "@bradleyhodges/sfsymbols-react";
import { sfArrowUpCircleFill } from "@bradleyhodges/sfsymbols/sfArrowUpCircleFill";
<SFIcon icon={sfArrowUpCircleFill} size={24} color="rebeccapurple" />;
Do not build an application registry that imports every icon merely to support runtime lookup. Import the icons used by the application, or load bounded groups on demand.
Styled and unstyled entries
The root entry merges these compatibility classes with className by using the cn engine with a default base class set: inline-block align-middle overflow-visible text-current box-content h-[1em] -leading-[0.125em]. The default h-[1em] class is omitted when size or a native height is supplied, and caller sizing utilities win class conflicts.
The unstyled entry renders the same SVG, paths, accessibility markup, ref, and events without importing cn or adding classes:
import SFIcon from "@bradleyhodges/sfsymbols-react/unstyled";
<SFIcon icon={sfArrowUpCircleFill} className="icon" />;
Use the root entry for the established layout behavior. Use /unstyled when the application owns all SVG layout and wants the smaller boundary.
Props and precedence
SFIcon accepts ordinary React.SVGProps<SVGSVGElement> plus the following props:
| Prop | Type | Behavior |
|---|---|---|
icon | IconDefinition | Required icon geometry and metadata. |
size | number | string | null | Sets both dimensions unless a native dimension overrides it. Explicit 0 is preserved. |
width, height | native SVG values | Override size independently. |
color | CSS color | Overrides each path's source fill. |
weight | number | null | Adds root fill, stroke, stroke width and round joins. It is a stroke effect, not selection of Apple's native SF Symbols weights. |
fillOpacity | string | number | null | Overrides source path opacity. With weight, the legacy default is 1. |
preservePathOpacity | boolean | Keeps source opacity when weight is set and fillOpacity is absent. |
className, style | native SVG values | Apply to the SVG. The styled entry merges classes; ordinary CSS cascade and inline-style rules still apply. |
title | string | Renders <title> and generates its linked ID. |
description | string | Renders <desc> and generates its linked ID. A description alone does not name an image; also supply title, aria-label, or an external accessible name. |
titleId, descriptionId | string | Replace the generated ID for the corresponding element. They have no effect when that element is absent. |
svgChildren | ReactNode | Inserts SVG content such as defs before the icon paths. Legacy children remain ignored. |
pathProps | path props or (path, index) => path props | Applies safe per-path presentation and event props after generated fill and opacity. |
Dimension precedence is native width or height, then size, then the icon's positive width, then its positive height, then 1em. The definition fallback is deliberately square for compatibility. A zero definition dimension is not used as the fallback, while an explicit size={0}, width={0}, or height={0} remains zero.
Path fill precedence is pathProps.fill, color, source fill, then currentColor. Path opacity precedence is pathProps.fillOpacity, explicit fillOpacity, the legacy weight default unless preservePathOpacity is true, then source opacity. pathProps.style follows ordinary React style precedence. Geometry and child ownership stay with the renderer: d, key, ref, children, and dangerouslySetInnerHTML are excluded by the public type and filtered at runtime.
Native SVG props, including refs and event handlers, are forwarded. Explicit role, aria-hidden, aria-label, aria-labelledby, and aria-describedby override generated accessibility attributes. When title and aria-label are both present, the title's generated aria-labelledby takes naming precedence; explicitly override aria-labelledby, including with aria-labelledby={undefined}, when aria-label should supply the name. Without an accessible name the SVG is hidden; a title, aria-label, or external aria-labelledby makes it visible to accessibility APIs. Generated IDs are unique, stable across title removal and restoration, and can be replaced with deterministic IDs when server output needs them.
const iconRef = React.createRef<SVGSVGElement>();
<SFIcon
ref={iconRef}
icon={sfArrowUpCircleFill}
title="Upload"
description="Moves the selected item upward"
svgChildren={
<defs>
<linearGradient id="upload-gradient">...</linearGradient>
</defs>
}
pathProps={(path, index) => ({
fill: index === 0 ? "url(#upload-gradient)" : path.fill,
})}
/>
Plain serializable props and SVG children can render through React Server Components. Functions such as pathProps callbacks and event handlers must be introduced on the client side under the application's normal Server/Client boundary; the package does not add a use client directive.
Metadata helpers and types
The root and /metadata entries export getIconKeywords and getIconVariants. Both validate legacy catalogue shapes without mutating them, ignore inherited properties and accessors, preserve source order, and return frozen copies.
import {
getIconKeywords,
getIconVariants,
} from "@bradleyhodges/sfsymbols-react/metadata";
const keywords = getIconKeywords(sfArrowUpCircleFill);
const variants = getIconVariants<"custom">(sfArrowUpCircleFill);
Keyword arrays retain valid text, generic, and finite priority fields. Legacy string dictionaries become { text } entries without invented metadata. Variant maps retain own string values and use a null prototype, so arbitrary absent custom names such as toString safely resolve to undefined. Convert with { ...variants } when an API specifically requires an ordinary object prototype.
React Server Component props are one such boundary because React's serializable object form requires an ordinary object prototype:
<ClientComponent variants={{ ...getIconVariants(icon) }} />
Public types are available from the root, /unstyled, and the type-only /types entry: IconDefinition, SFIconProps, SFIconPathProps, SFIconPath, SFIconKeyword, and SFIconVariant. The additive SFIconProps type keeps the companion package's legacy contract intact.
Optional VS Code previews
The separate extension in extensions/vscode provides local hover previews and SF Symbols: Preview and Import Icon. It parses installed icon leaves as bounded static data and inserts exact per-icon imports; it is not an application dependency and does not ship an icon registry.
To build a local VSIX without installing or publishing it:
cd extensions/vscode
pnpm install --frozen-lockfile
pnpm test
pnpm typecheck
pnpm package
Then choose Extensions: Install from VSIX… in VS Code and select artifacts/sfsymbols-preview-0.1.0.vsix. See the extension README for supported syntax, security boundaries, limits, corpus checks, and the isolated extension-host workflow.