@serenis/icons

中文文档
2.62.0发布于 2026/11/02SEE LICENSE IN LICENSETypeScriptESM + CJS

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

原始使用文档
文档目录25 节
  1. Install
  2. Quick start
  3. Directory structure
  4. Architecture
  5. Two icon sources
  6. The Icon wrapper
  7. Dual export pattern
  8. Type system
  9. Size scale
  10. Usage patterns
  11. Direct rendering
  12. Passing icons as props
  13. Active / inactive pattern
  14. Aliasing when names clash
  15. Inheriting color from parent
  16. Accessibility
  17. Icon with text (no extra ARIA needed)
  18. Icon-only controls need aria-label
  19. Why the Icon wrapper has no ARIA attributes
  20. ESLint enforcement
  21. How to add a new icon
  22. Build
  23. Consumers
  24. Documentation
  25. License

@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

Quick start

# Build
yarn workspace @serenis/icons build

# Dev (watch mode)
yarn workspace @serenis/icons dev

# Typecheck
yarn workspace @serenis/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:

SourceWhere they liveIcon* shorthand?
Custom Serenissrc/components/*.tsx (inline SVG)Yes — e.g. Home + IconHome
IconoirRe-exported from iconoir-reactNo — 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 @serenis/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'
TypePurpose
IconSize12 | 16 | 20 | 24 | 32 | 40 | 48 | 56 | 64 | 72 | 80 | 120
IconElementPropsLow-level contract for SVG components: { color, fill, height, width }
IconElementAny component that accepts IconElementProps — use for props that accept an icon reference
IconCommonPropsToken-based API: { colorName?, fillColorName?, size }
IconPropsIconCommonProps & { 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 @serenis/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:

<span className={cx('text-primary')}>
  <Icon Svg={Home} size={24} /> {/* stroke is primary */}
</span>

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>

<div className={cx('flex', 'items-center', 'gap-xs')}>
  <Icon Svg={Info} size={24} />
  <span>Important information</span>
</div>

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 @serenis/icons build
yarn workspace @serenis/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 (@serenis/design-tokens, iconoir-react, react) are externalized.

Consumers

  • Apps: apps/web (primary consumer), apps/blog, apps/nutrition-blog, apps/website
  • Libraries: libraries/ui (@serenis/ui — Button, Modal, Accordion, Breadcrumbs, FileUploadField, etc.), libraries/shared-components (@serenis/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.

版本与发布记录

@serenis/icons 共发布 21 个版本,首次发布于 2026/07/03;过去 12 个月发布了 21 个版本。

版本发布日期距今
2.62.0latest2026/11/021 天前
2.61.02026/10/285 天前
2.60.02026/10/268 天前
2.59.02026/10/2310 天前
2.58.02026/10/2311 天前
2.57.02026/10/2212 天前

关于 @serenis/icons 的常见问题

@serenis/icons 是什么?

用于Serenis设计系统的图标组件库。包装自定义SVG图标并从iconoir-react重新导出。

如何安装 @serenis/icons?

在项目目录运行 npm install @serenis/icons,或使用 pnpm add / yarn add / bun add。它还需要宿主项目提供 peer 依赖:react(>=19.0.0)、@serenis/design-tokens(*)。npm 7 及以上与 pnpm 8 及以上默认会自动安装必需的 peer 依赖,Yarn 需要手动安装。

@serenis/icons 支持 TypeScript 吗?

支持。包内自带 TypeScript 类型声明(package.json 声明了 types 入口),安装后即可获得类型提示,无需再安装 @types 包。

@serenis/icons 支持 ESM 还是 CommonJS?

同时提供 ES Module 与 CommonJS 入口,import 和 require() 都可以使用,打包工具会自动选择合适的版本。

@serenis/icons 需要什么版本的 Node.js?

package.json 的 engines 字段要求 Node.js >=22。版本不符时 npm 默认只给出警告,开启 engine-strict 后会拒绝安装。

@serenis/icons 可以商用吗?

@serenis/icons 使用 SEE LICENSE IN LICENSE 许可。许可条款写在包内的独立文件中,请阅读该文件确认可否商用。以上为概括,不构成法律意见,请以许可原文为准。

@serenis/icons 还在维护吗?

最新版本 2.62.0 发布于 2026/11/02(1 天前);至今共发布 21 个版本,其中过去 12 个月发布了 21 个;首次发布于 2026/07/03。发布频率只是参考信号:功能稳定的工具可能很少需要更新,建议结合源码仓库的 issue 和提交记录判断。