Icons in SolidJS
There is no dedicated Solid package yet, and you can do without one. The <glyph-fox> element works in Solid templates, and a component of a few lines on top of glyphfox/core gives you reactive, tree shakable icons that render on the server.
Install
Install the core package. Everything on this page uses entry points that ship in it: glyphfox/web for the element, and glyphfox/core with glyphfox/icons/NAME for the component.
npm install glyphfox
| Aspect | glyph-fox element | Glyph component |
|---|---|---|
| Renders | In the browser, when the element upgrades. | On the server and in the browser. |
| Ships | The element, its engine and the icons you register. | The renderer and the icon modules you import. |
| Motion | Morphs on every name change, hover and click triggers, states. | CSS motion inside the SVG: presets and hover morphs. |
| Good for | Icons that react to the user. | Most icons of an app. |
The glyph-fox element
Register the element once, in your entry file. glyphfox/web registers every icon; to keep the bundle to the icons you list, use glyphfox/web/element with registerIcons, as shown in the web components guide.
import { render } from 'solid-js/web'; import 'glyphfox/web'; import App from './App'; render(() => <App />, document.getElementById('root'));
Static attributes are compiled into the template, so they reach the element as attributes and work as written:
export function Toolbar() { return ( <div class="toolbar"> <glyph-fox name="settings" size="20" weight="1.5" /> <glyph-fox name="bell" animate="shake" trigger="hover" /> </div> ); }
Dynamic values are different. On custom elements, Solid assigns expressions as properties, and <glyph-fox> has no properties: it reads attributes. Prefix dynamic props with the attr: namespace so Solid sets the attribute instead.
import { createSignal } from 'solid-js'; export function MenuButton() { const [open, setOpen] = createSignal(false); return ( <button type="button" aria-expanded={open()} aria-label={open() ? 'Close menu' : 'Open menu'} onClick={() => setOpen(!open())} > <glyph-fox attr:name={open() ? 'x' : 'menu'} morph duration="300" /> </button> ); }
The morph attribute makes the element animate every change of name, so each toggle of the signal plays the hand tuned menu to close morph. Events need nothing special: onClick on a parent works as usual, and triggers such as trigger="click" are handled inside the element.
TypeScript
Solid JSX types do not know glyph-fox. Declare it once in a declaration file, and list the attributes you set through attr: in ExplicitAttributes, the interface Solid reads for that namespace.
import 'solid-js'; type GlyphFoxAttributes = { name?: string; size?: string | number; color?: string; weight?: string | number; variant?: 'outline' | 'duotone' | 'solid'; animate?: 'draw' | 'pop' | 'wiggle' | 'spin' | 'pulse' | 'bounce' | 'shake'; trigger?: 'mount' | 'hover' | 'click' | 'state'; morph?: boolean | string; 'morph-to'?: string; duration?: string | number; }; declare module 'solid-js' { namespace JSX { interface IntrinsicElements { 'glyph-fox': JSX.HTMLAttributes<HTMLElement> & GlyphFoxAttributes; } interface ExplicitAttributes { name: string; size: string | number; active: boolean | string; } } }
A small Glyph component
For icons that do not need the interaction built into the element, render the SVG yourself. renderToString from glyphfox/core turns icon data into markup with every shared prop, and each glyphfox/icons/NAME module holds the data of one icon, so the bundle carries only what you import.
import { splitProps } from 'solid-js'; import { renderToString } from 'glyphfox/core'; export function Glyph(props) { const [local, options] = splitProps(props, ['icon', 'class']); return ( <span class={local.class} style={{ display: 'inline-flex', 'line-height': '0' }} innerHTML={renderToString(local.icon, options)} /> ); }
import { Glyph } from './Glyph'; import home from 'glyphfox/icons/home'; import bell from 'glyphfox/icons/bell'; export function Nav(props) { return ( <nav> <a href="/"> <Glyph icon={home} size={20} /> Home </a> <button type="button" aria-label="Notifications"> <Glyph icon={bell} animate="shake" trigger="state" active={props.unread > 0} /> </button> </nav> ); }
The effect behind innerHTML tracks every prop that renderToString reads, so a change of active, size or icon rewrites the markup. The markup is replaced, not tweened. For a morph between two icons, use the element, or a CSS morph on hover with morphTo:
import { Glyph } from './Glyph'; import arrowRight from 'glyphfox/icons/arrow-right'; import chevronRight from 'glyphfox/icons/chevron-right'; export const NextLink = () => ( <a href="/next" class="next"> Next <Glyph icon={arrowRight} morphTo={chevronRight} trigger="hover" /> </a> );
With glyphfox/core, morphTo takes icon data, not a name: only toSvg in the main entry resolves names. A name passed here is ignored and the icon stays still. The CSS morph plays while the pointer is over the svg or over its direct parent, which in this component is the wrapper span.
The name syntax (file+check@tr, cloud&lock) is also resolved by toSvg. With the core, use the matching props and helpers instead: modifier and modifierCorner for badges, fuse for fusions.
import { fuse } from 'glyphfox/core'; import { Glyph } from './Glyph'; import file from 'glyphfox/icons/file'; import cloud from 'glyphfox/icons/cloud'; import lock from 'glyphfox/icons/lock'; const secureCloud = fuse([cloud, lock], { layout: 'overlap' }); export const Status = () => ( <p> <Glyph icon={file} modifier="check" modifierCorner="top-right" title="Saved" /> <Glyph icon={secureCloud} size={32} title="Encrypted backup" /> </p> );
SolidStart and server rendering
The Glyph component renders on the server like any other markup: SolidStart writes the SVG into the HTML, so icons are part of the first paint and the layout does not move when the page hydrates.
The element is a browser feature. It upgrades after its script runs and does not use declarative shadow DOM, so server HTML contains the empty tag. Reserve its box with CSS, or keep the icons that must be visible at once on the Glyph component. The trade offs for every framework are in server rendering.
glyph-fox:not(:defined) { display: inline-block; width: 24px; height: 24px; }
Importing glyphfox/web on the server is harmless: the element checks for customElements and does nothing without it. Still, import it from client code, such as src/entry-client.tsx in SolidStart, so the element and its icons stay out of the server bundle.
Props
The Glyph component passes its props to renderToString, so it takes the shared options with their camelCase names. On the element the same props are attributes in kebab-case, listed in the web components guide.
| Option | Type | Default | Description |
|---|---|---|---|
size | number, string | 24 | Width and height; numbers are pixels. |
color | string | currentColor | Main color. |
secondaryColor | string | color | Color of the duotone layer and of the solid fill. |
secondaryOpacity | number | 0.3 / 1 | Opacity of the duotone layer or of the solid fill. |
weight | number | 2 | Stroke from 1 to 3 on the 24 grid, optically compensated below 24 px. |
absoluteWeight | boolean | false | Keeps the stroke at weight pixels at any size. |
corners | round, sharp | round | Round or sharp joins and caps. |
variant | outline, duotone, solid | outline | Style axis; solid comes from the closed shapes. |
rotate | number | 0 | Rotation in degrees. |
flipX | boolean | false | Mirror horizontally. |
flipY | boolean | false | Mirror vertically. |
mirrorRtl | boolean | false | Mirror in right to left layouts. |
animate | draw, pop, wiggle, spin, pulse, bounce, shake | none | Living state preset. |
trigger | mount, hover, click, state | mount | When the animation or the morph plays. |
active | boolean | false | The state for trigger state. |
duration | number (ms) | per preset | Animation or morph duration. |
delay | number (ms) | 0 | Animation or morph delay. |
loop | boolean, number | false | Repeat forever or a number of times (spin and pulse loop by default). |
modifier | plus, minus, check, x, alert, lock, clock, sync, arrow-up, arrow-down, dot | none | Badge with an automatic knockout gap. |
modifierCorner | top-left, top-right, bottom-left, bottom-right | bottom-right | Corner of the badge. |
morphTo | name, data | none | Morph target; the trigger decides when it shows. |
title | string | none | Accessible name: title, role img and aria-label. |
decorative | boolean | true without a title | Hides the icon from assistive technology. |
Pitfalls
- Dynamic props without attr:
name={...}onglyph-foxsets a property the element ignores, and the icon renders empty. Writeattr:name={...}. Static strings work without the prefix.- A name in morphTo
renderToStringfrom the core needs icon data formorphTo. A string is ignored. Import the target icon and pass its data.- Mount animations replay
- Every prop change replaces the
innerHTML, so a preset with the defaulttrigger="mount"plays again. For animations tied to state, usetrigger="state"withactive. - Every icon by accident
import { toSvg } from 'glyphfox'brings every icon into the bundle, because a name can point to any of them. In client code, use the core with per icon modules; keeptoSvgfor server only code.- The element on the server
- Server HTML holds an empty
<glyph-fox>until the script runs. Reserve its size, or render server visible icons with the Glyph component. - The wrapper span
- The Glyph component wraps the svg in a span: flex and grid gaps apply to the span, and
classlands on the span, not the svg. Set sizes through props rather than with CSS on the span.
Questions
Is there a glyphfox package for Solid?
Not yet. Both approaches on this page use what the glyphfox package ships today: the custom element, and the core renderer that the React, Vue and Svelte packages are built on.
Do icons update when a signal changes?
Yes. With the element, bind through attr: and the element redraws on each attribute change, with a morph when the morph attribute is set. With the Glyph component, the effect behind innerHTML tracks the props that the renderer reads and rewrites the markup.