npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@serenis/icons

v2.42.0

Published

Icon component library for the Serenis design system. Wraps custom SVG icons and re-exports from iconoir-react.

Readme

@serenis/icons

Icon component library for the Serenis design system. Wraps custom SVG icons and re-exports from iconoir-react.

Install

npm install @serenis/icons

Peer dependencies (must be installed by the consuming app):

  • react >= 19.0.0
  • @serenis/design-tokens

Quick start

# Build
yarn workspace icons build

# Dev (watch mode)
yarn workspace icons dev

# Typecheck
yarn workspace icons typecheck
import { Icon, Home, IconHome } from '@serenis/icons'
import { type IconElement, type IconCommonProps, type IconSize } from '@serenis/icons'

// Option A: Icon wrapper with raw SVG component (dynamic / slotted usage)
<Icon Svg={Home} colorName="primary" size={24} />

// Option B: Pre-wrapped shorthand (fixed icon usage)
<IconHome colorName="primary" size={24} />

Directory structure

libraries/icons/
├── src/
│   ├── components/           # One .tsx file per icon + Icon.tsx wrapper
│   │   ├── Icon.tsx          # Design-token-aware wrapper
│   │   ├── Home.tsx          # Example: exports Home + IconHome
│   │   ├── Agenda.tsx
│   │   └── ...               # custom icon files
│   ├── stories/
│   │   ├── Overview.mdx      # Storybook documentation page
│   │   ├── Playground.stories.tsx # Storybook playground for all icons
│   │   └── ...
│   ├── types.ts              # Shared TypeScript types
│   └── index.ts              # Public API (re-exports everything)
├── dist/                     # Built output (gitignored)
├── tsup.config.ts            # Bundle config
└── package.json

Architecture

Two icon sources

The library ships icons from two sources through a single public API:

| Source | Where they live | Icon* shorthand? | | ------------------ | ----------------------------------- | --------------------------------- | | Custom Serenis | src/components/*.tsx (inline SVG) | Yes — e.g. Home + IconHome | | Iconoir | Re-exported from iconoir-react | No — use via <Icon Svg={...} /> |

Custom icons are hand-maintained inline SVG React components designed specifically for the Serenis product. Iconoir icons fill gaps for generic UI glyphs (arrows, navigation chrome, utility actions) that don't need custom artwork.

The Icon wrapper

Icon is the bridge between raw SVG components and the design token system. It accepts any IconElement and applies design-token-based color and size:

const Icon = ({ Svg, colorName, fillColorName, size }: IconProps): JSX.Element | null => {
  if (Svg == null) {
    return null
  }

  return (
    <Svg
      color={colorName ? cssvarColor(colorName) : 'currentColor'}
      fill={fillColorName ? cssvarColor(fillColorName) : 'none'}
      height={size}
      width={size}
    />
  )
}
  • colorName — maps to color (stroke) via cssvarColor() from design-tokens. Omitting it defaults to currentColor, inheriting from the parent text color.
  • fillColorName — maps to fill via cssvarColor(). Omitting it defaults to none. Rarely used (stars, a few nav icons).
  • size — applied to both width and height.

Dual export pattern

Every custom icon file exports two things:

// Home.tsx
export const Home = ({ color, height, width }: IconElementProps) => (
  <svg ...>...</svg>
)

export const IconHome = (props: IconCommonProps) => <Icon Svg={Home} {...props} />
  1. Home — the raw SVG component. Use when passing as a reference to the Icon wrapper or to component props typed as IconElement (e.g. Button's IconStart).
  2. IconHome — pre-wrapped shorthand. Use for direct rendering when you don't need to pass the icon as a reference.

Iconoir icons are only exported as raw components (no Icon* shorthand).

Type system

All types are exported from the package root:

import {
  type IconElement,
  type IconElementProps,
  type IconCommonProps,
  type IconProps,
  type IconSize,
} from '@serenis/icons'

| Type | Purpose | | ------------------ | ------------------------------------------------------------------------------------------- | | IconSize | 12 \| 16 \| 20 \| 24 \| 32 \| 40 \| 48 \| 56 \| 64 \| 72 \| 80 \| 120 | | IconElementProps | Low-level contract for SVG components: { color, fill, height, width } | | IconElement | Any component that accepts IconElementProps — use for props that accept an icon reference | | IconCommonProps | Token-based API: { colorName?, fillColorName?, size } | | IconProps | IconCommonProps & { Svg?: IconElement } — the Icon wrapper props |

Size scale

| Size | | ---------------- | | 12 | | 16 | | 20 | | 24 (default) | | 32 | | 40 | | 48 | | 56 | | 64 | | 72 | | 80 | | 120 |

Usage patterns

Direct rendering

// Pre-wrapped shorthand (custom icons only)
<IconHome colorName="primary" size={24} />

// Icon wrapper (both custom and Iconoir)
<Icon Svg={Home} colorName="primary" size={24} />
<Icon Svg={ArrowLeft} colorName="neutral-80" size={24} />

Passing icons as props

Many ui components accept IconElement as a prop:

// Button with start icon
<Button IconStart={Home} kind="primary">Go home</Button>

// Tile with required icon
<Tile Icon={Shield} title="Security" action={<Button>Configure</Button>} />

// SideBarItem with active/inactive variants
<SideBarItem Icon={Agenda} IconActive={AgendaSolid} label="Agenda" active={isActive} />

Active / inactive pattern

Many custom icons have a Solid variant for active states:

<Icon Svg={active ? AgendaSolid : Agenda} colorName={active ? 'primary-50' : 'neutral-110'} size={24} />

Available pairs: Agenda/AgendaSolid, Calendar/CalendarSolid, Chat/ChatSolid, Clock/ClockSolid, Codes/CodesSolid, Delete/DeleteSolid, Diary/DiarySolid, Home/HomeSolid, Menu/MenuSolid, Patients/PatientsSolid, Paths/PathsSolid, and all Path*/Path*Solid therapy path icons.

Aliasing when names clash

Some icon names conflict with common identifiers. Alias on import:

import { Card as IconCard, Link as IconLink } from '@serenis/icons'

Inheriting color from parent

Omit colorName to use currentColor — the icon inherits the parent's text color:

<Text colorName="primary">
  <Icon Svg={Home} size={24} /> {/* stroke is primary */}
</Text>

Accessibility

Icons rendered through the Icon wrapper are decorative by default — they carry no accessible name or role. Meaning is expected to come from adjacent text or from the parent control's label.

Icon with text (no extra ARIA needed)

When an icon appears alongside visible text, the text provides the accessible name. No extra attributes are required on the icon:

<Button IconStart={Home} kind="primary">Go home</Button>

<Flex>
  <Icon Svg={Info} size={24} />
  <Text>Important information</Text>
</Flex>

Icon-only controls need aria-label

When an icon is the sole content of a clickable control, the parent element must provide an accessible name via aria-label or aria-labelledby:

<Button IconStart={Delete} kind="tertiary" aria-label="Delete item" />

<Pressable aria-label="Close" onClick={onClose}>
  <Icon Svg={ActionCloseLine} size={24} />
</Pressable>

Why the Icon wrapper has no ARIA attributes

The Icon component intentionally does not set aria-hidden, role, or any ARIA attribute. Whether an icon is decorative or meaningful depends on the usage context, which only the caller knows. Callers should:

  • Add aria-label to the parent control when the icon is the only content
  • Do nothing when adjacent text already conveys the meaning
  • Avoid adding aria-hidden="true" to icons that appear alongside text (the text already provides the name)

ESLint enforcement

The custom ESLint rule serenis/no-icon-inside-button (active as warn in libraries/ui, apps/web, apps/blog, apps/website, apps/nutrition-blog) prevents nesting Icon or Icon* components as children of Button. Use the IconStart / IconEnd props instead — this ensures consistent sizing, loading-state handling, and accessible markup:

// Correct
<Button IconStart={Home} kind="primary">Go home</Button>

// Incorrect — caught by ESLint
<Button><Icon Svg={Home} size={24} />Go home</Button>

How to add a new icon

There is no codegen pipeline — icons are hand-maintained TSX files.

  1. Create the component file at src/components/MyIcon.tsx:
import { type IconCommonProps, type IconElementProps } from '../types'
import Icon from './Icon'

type Props = IconElementProps

export const MyIcon = ({ color, height, width }: Props) => (
  <svg fill="none" height={height} viewBox="0 0 24 24" width={width} xmlns="http://www.w3.org/2000/svg">
    {/* Paste SVG paths here, use color={color} for strokes */}
  </svg>
)

export const IconMyIcon = (props: IconCommonProps) => <Icon Svg={MyIcon} {...props} />
  1. Export from src/index.ts (keep alphabetical order):
export { IconMyIcon, MyIcon } from './components/MyIcon'
  1. Build and typecheck:
yarn workspace icons build
yarn workspace icons typecheck

The new icon will automatically appear in the Storybook Playground story.

Build

The package is bundled with tsup into ESM + CJS + TypeScript declarations. Workspace dependencies (design-tokens, iconoir-react, react) are externalized.

Consumers

  • Apps: apps/web (primary consumer), apps/blog, apps/nutrition-blog, apps/website
  • Libraries: libraries/ui (Button, Modal, Accordion, Breadcrumbs, FileUploadField, etc.), libraries/shared-components (Nav)

Documentation

  • Storybook: Overview page and interactive Playground in apps/design-system
  • AGENTS.md: Agent-oriented API reference and rules — see AGENTS.md

License

Custom Serenis SVG icons: CC BY-NC-ND 4.0 — see LICENSE for the full text. Iconoir re-exports retain their upstream MIT license.