@alma-oss/spirit-web-react

中文文档
5.5.0发布于 2026/10/19MITTypeScriptESM + CJS构建来源已验证近 30 天下载 3369

React implementation of Spirit Design System components

原始使用文档
文档目录32 节
  1. Install
  2. Usage
  3. Prefixing CSS Classes in Components
  4. Client Side Routing
  5. Translations
  6. Placeholders
  7. Component Strings
  8. Additional Attributes
  9. Shared and Inherited Props
  10. Global Props
  11. Namespaced Props
  12. Overriding the Namespace with propsContext
  13. Precedence
  14. How ContextPropsProvider Merges Values
  15. Group Contexts (Shared Values Outside a Namespace)
  16. Testing
  17. End-to-End Testing
  18. Styling
  19. Style Props
  20. Types Generated From Design Tokens
  21. Escape Hatches
  22. Utilities
  23. pxToRem()
  24. Controlled vs Uncontrolled Components
  25. Deprecations
  26. Warnings in Environments
  27. Examples
  28. Implementing From Figma
  29. Figma Code Connect
  30. AI-Assisted Implementation
  31. Summary
  32. License

@alma-oss/spirit-web-react

minified minified + gzip dependency count tree shaking client components

React implementation of Spirit Design System components.

Install

Expecting you have react and react-dom installed in your app, run:

yarn add @alma-oss/spirit-web @alma-oss/spirit-web-react

or

npm install --save @alma-oss/spirit-web @alma-oss/spirit-web-react

Usage

Link Spirit CSS (see spirit-web docs for more options):

<link rel="stylesheet" href="node_modules/@alma-oss/spirit-web/css/foundation.min.css" />
<link rel="stylesheet" href="node_modules/@alma-oss/spirit-web/css/components.min.css" />
<link rel="stylesheet" href="node_modules/@alma-oss/spirit-web/css/helpers.min.css" />
<link rel="stylesheet" href="node_modules/@alma-oss/spirit-web/css/utilities.min.css" />

⚠️ Make sure to load all CSS files above and in the same order.

Import React components in your app:

import { Button } from '@alma-oss/spirit-web-react';

Prefixing CSS Classes in Components

If you want to prefix the component classes with your own namespace, you can use the ClassNamePrefixProvider context to provide a prefix to all components in your app.

Check spirit-web docs to learn how to prefix CSS class names.

import { ClassNamePrefixProvider } from '@alma-oss/spirit-web-react/context/ClassNamePrefixContext';

export const Example = () => {
  return (
    <ClassNamePrefixProvider value="jobs">
      <Button>Button</Button>
    </ClassNamePrefixProvider>
  );
};

Client Side Routing

If you want to use client-side routing with router-integrated link-like components (for example Link, ButtonLink, PaginationLink, HeaderLink, or TabLink), you can use the RouterProvider context to provide a router navigation function. When a router is provided, internal links will use client-side navigation instead of full page reloads.

The RouterProvider accepts the following props:

  • children - React node subtree that should receive the router integration (required)
  • navigate - A function that accepts a path string and optional router options

External links (absolute http:// / https:// URLs, protocol-relative URLs like //example.com, or URLs with other schemes such as mailto: and tel:), links with non-_self target, links with download, and disabled links will always use standard anchor behavior.

Router Options

All link components accept a routerOptions prop, which is an object that is passed through to the client side router's navigate function as the second argument. This can be used to control any router-specific behaviors, such as scrolling, replacing instead of pushing to the history, etc.

<Link href="/login" routerOptions={{ replace: true }}>
  Login
</Link>

Next.js App Router

'use client';

import { Link, RouterProvider } from '@alma-oss/spirit-web-react';
import { useRouter } from 'next/navigation';

const MyComponent = () => {
  const router = useRouter();

  return (
    <RouterProvider navigate={router.push}>
      <Link href="/about">About</Link>
      <Link href="/contact">Contact</Link>
    </RouterProvider>
  );
};

If you use the Next.js basePath setting, include it directly in Spirit href values so rendered anchor URLs stay correct:

'use client';

import { Link, RouterProvider } from '@alma-oss/spirit-web-react';
import { useRouter } from 'next/navigation';

// Same string as `basePath` in `next.config.js` (leading slash, no trailing slash)
const basePath = '/docs';

const MyComponent = () => {
  const router = useRouter();

  return (
    <RouterProvider navigate={router.push}>
      <Link href={`${basePath}/about`}>About</Link>
    </RouterProvider>
  );
};

Next.js Pages Router

In the Pages Router, router.push is (url, as?, options?): the second argument is the optional as path, and transition options (e.g. shallow) belong in the third argument. Spirit passes routerOptions from link components as the second argument to navigate, so you should not pass router.push directly—use a thin wrapper that forwards options as the third parameter:

import { Link, RouterProvider } from '@alma-oss/spirit-web-react';
import { useRouter } from 'next/router';

const MyComponent = () => {
  const router = useRouter();
  const navigate = (path, opts) => router.push(path, undefined, opts);

  return (
    <RouterProvider navigate={navigate}>
      <Link href="/about">About</Link>
      <Link href="/contact" routerOptions={{ shallow: true }}>
        Contact
      </Link>
    </RouterProvider>
  );
};

React Router

Use navigate from your router. Keep href values in the format expected by your app/router configuration.

import { Link, RouterProvider } from '@alma-oss/spirit-web-react';
import { useNavigate } from 'react-router-dom';

const MyComponent = () => {
  const navigate = useNavigate();

  return (
    <RouterProvider navigate={navigate}>
      <Link href="/about">About</Link>
    </RouterProvider>
  );
};

Remix

Remix re-exports compatible navigation helpers; use navigate in the same way:

import { Link, RouterProvider } from '@alma-oss/spirit-web-react';
import { useNavigate } from '@remix-run/react';

const MyComponent = () => {
  const navigate = useNavigate();

  return (
    <RouterProvider navigate={navigate}>
      <Link href="/about">About</Link>
    </RouterProvider>
  );
};

Translations

Components ship with built-in English defaults. To override copy, wrap your app or a subtree with I18nProvider. You can pass either:

  • partial nested translations (same nested shape as library defaults), or
  • a locale catalog with locale + locale-keyed translations (translations[locale]).

Missing keys always fall back to built-in defaults. A locale catalog with no entry for the active locale also uses those defaults — the catalog is not merged as translation namespaces.

import { I18nProvider } from '@alma-oss/spirit-web-react';

function App() {
  return (
    <I18nProvider translations={{ common: { close: 'Zavřít' } }}>
      <YourApp />
    </I18nProvider>
  );
}
import { I18nProvider } from '@alma-oss/spirit-web-react';

const translations = {
  en: { common: { close: 'Close' } },
  cs: { common: { close: 'Zavřít' } },
};

function App() {
  return (
    <I18nProvider locale="cs" translations={translations}>
      <YourApp />
    </I18nProvider>
  );
}

In client components, use useI18n to resolve strings. Without I18nProvider, t uses the built-in defaults only.

import { useI18n } from '@alma-oss/spirit-web-react';

const { t } = useI18n();

t('common.close'); // 'Close'
t('unknown.key'); // 'unknown.key' (returns the key when not found)

Placeholders

The t function accepts an optional second argument params: an object whose keys match {key} placeholders in the resolved string.

t('textArea.counter.characters.enteredAria', { count: 42 });

Component Strings

Required component copy is exposed as a top-level prop, while optional copy with a built-in default is grouped in the strings prop. Both accept a literal string or a translation reference with optional parameters.

<UncontrolledSplitButton labelButton="Save" />
<UncontrolledSplitButton labelButton={{ key: 'actions.save' }} />
<ModalHeader strings={{ closeAria: 'Dismiss dialog' }} />
<ModalHeader strings={{ closeAria: { key: 'common.close' } }} />
<ModalHeader strings={{ closeAria: { key: 'modal.close', params: { name: 'Settings' } } }} />

Translation references can use any application or Phrase key provided through I18nProvider; they are not limited to the built-in namespaces. Resolution precedence is instance strings, deprecated alias during the v5 migration, provider translation, and finally the built-in English default.

The Aria suffix marks text that is not displayed and exists only as an accessible name, description, or instruction. Visible copy has no suffix. Related strings are grouped by purpose. Each component README lists the keys, dictionary defaults, and English copy in a Translations section. Dictionary paths match the strings object (picker.tag.removeAria). Previous paths such as picker.removeItemLabel still resolve.

Additional Attributes

All components accept additional attributes that are passed down to the root element of the component. This is useful for adding custom event handlers, accessibility attributes, or other attributes that are not supported by the component API.

ℹ️ If you need to pass down event handlers to the native form elements in our form components, you can use the inputProps prop.

Supported attributes are:

  • on* (eg. onClick)
  • data-*
  • aria-*
  • id

If the component sets a value for any of these attributes, the value passed in will be overwritten.

Most components also accept native HTML attributes based on the component's element type.

Shared and Inherited Props

Some components pick up prop values from an ancestor component instead of (or in addition to) props passed to them directly — for example, a Label nested inside a TextField automatically becomes disabled when the TextField is disabled, without you passing isDisabled to the Label yourself. This section explains the two mechanisms that make this work — namespaced props via ContextPropsProvider, and group contexts — and how they combine.

Global Props

A fixed set of props — isDisabled, isRequired, validationState — cascade to every descendant component automatically, regardless of that component's namespace (see below). Set them once on a producer component (eg. TextFieldBase) and every nested Label, HelperText, ValidationText, etc. picks them up without further wiring.

Namespaced Props

Most other shared props are namespaced: a producer component provides a ContextPropsProvider value keyed by the namespace of the component it targets, and only a consumer reading that namespace receives it.

<ContextPropsProvider value={{ label: { isLabelHidden: true } }}>
  {/* Only a component reading the "label" namespace picks up isLabelHidden */}
</ContextPropsProvider>

Each consuming component's default namespace is the camelCase form of its own name — e.g. Label reads from label, HelperText from helperText, InputContainer from inputContainer, and StackItem from stackItem.

Overriding the Namespace with propsContext

A consumer can read from a different namespace than its default by passing the propsContext prop, which lets you re-target props intended for a different component:

<ContextPropsProvider value={{ button: { color: 'danger' } }}>
  {/* This Label reads the "button" namespace instead of its default "label" namespace */}
  <Label propsContext="button">Delete</Label>
</ContextPropsProvider>

Precedence

Where more than one source provides the same prop, precedence is (low to high):

global props < namespace props < direct props

<ContextPropsProvider value={{ isDisabled: true, label: { isLabelHidden: true } }}>
  {/* isLabelHidden is inherited, but the direct prop below still wins over isDisabled */}
  <Label isDisabled={false}>Name</Label>
</ContextPropsProvider>

How ContextPropsProvider Merges Values

ContextPropsProvider cascades into whatever its parent already provided — it does not replace the whole inherited value. Top-level (global) keys replace the inherited value for that key; namespace keys merge one level deep with the inherited namespace object, so a nested provider can add or override individual namespace props without clobbering sibling props set by an ancestor. Setting a key to null/undefined removes it from what's inherited.

<ContextPropsProvider value={{ label: { isLabelHidden: true } }}>
  <ContextPropsProvider value={{ label: { isStretched: true } }}>
    {/* Label here inherits both isLabelHidden and isStretched */}
  </ContextPropsProvider>
</ContextPropsProvider>

Group Contexts (Shared Values Outside a Namespace)

A handful of related components share a single value — such as size or elementType — through a dedicated "group" context rather than through ContextPropsProvider. Unlike the closed set of global props, joining a group context is opt-in and open: any component, including one defined in your own app, can join a group simply by reading the context itself and merging the value in.

import { FormFieldsContext } from '@alma-oss/spirit-web-react';
import { useContext } from 'react';

const { size } = useContext(FormFieldsContext) ?? {};

A producer provides one or more group contexts at once via the Provider helper:

<Provider values={[[FormFieldsContext, { size: 'small' }]]}>{/* descendants can read FormFieldsContext */}</Provider>

The existing group contexts are:

ContextShared valueConsumersProducers
FormFieldsContextsizeInputAddon, InputContainer, ControlButton, LabelTextFieldBase, UNSTABLE_Picker, Select, UNSTABLE_Combobox
InlineElementsContextelementTypeLabel, HelperText, ValidationTextItem, Stack, StackItem
ListItemsContextelementTypeItem, StackItemStack, StackItem

InlineElementsContext and ListItemsContext both carry an elementType, but they answer different questions and are read by disjoint components: ListItemsContext tells Item/StackItem what element type they themselves should render as (e.g. li inside a ul Stack), while InlineElementsContext tells their inline content (Label, HelperText, ValidationText) what element type it should render as (e.g. span inside an Item). They're kept separate so a component never accidentally reads a value that was meant for its own descendants — Item provides InlineElementsContext: { elementType: 'span' } for its children without also seeing that value applied to itself.

Testing

End-to-End Testing

For detailed information about E2E tests including visual regression and accessibility testing, please refer to docs/contribution/e2e-testing.md and docs/contribution/accessibility-testing.md.

Styling

Spirit components are designed to be consistent across all Alma Career applications. They include built-in styling that has been considered carefully, and extensively tested. In general, customizing Spirit design is discouraged, but most components do offer control over layout and other aspects. In addition, you can use Spirit defined design tokens to ensure your application conforms to design requirements, and is adaptive across platform scales and color schemes.

Style Props

All Spirit components accept a set of props that can be used to control their outer spacing and display. The props are:

Theme Prop

  • theme

The theme prop applies a theme utility class generated from the design tokens. Use kebab-case values that mirror the token keys (eg. theme-light-default, theme-light-on-brand). You can apply the theme prop at any nest level where the component consumes style props and children components inherit the theme.

Spacing Props

  • margin
  • marginTop
  • marginRight
  • marginBottom
  • marginLeft
  • marginX
  • marginY

These props accept a spacing token (eg. space-100), auto or an object with breakpoint keys and spacing token values or auto. We use these props to set global CSS utility classes on the root element of the component.

Display Props

  • hideOn - Hide the component only at specific breakpoint(s)
  • hideFrom - Hide the component from a specific breakpoint and up

The hideOn prop accepts either a single breakpoint token (e.g. mobile, tablet, desktop), or an array of breakpoint tokens. The hideFrom prop accepts a single breakpoint token. The component will be hidden from the specified breakpoints up using CSS display utilities.

Examples

// Spacing examples
<Alert marginBottom="space-100" />
<Button marginX={{ mobile: 'space-100', tablet: 'space-200' }} />

// Hide examples
<Alert hideOn="mobile" />  // Hidden only on mobile
<Button hideOn={['mobile', 'tablet']} />  // Hidden only on mobile and tablet
<Alert hideFrom="tablet" />  // Hidden from tablet breakpoint and up

// Theme examples
<Header theme="theme-light-default" />
<Header theme="theme-light-on-brand" />

If you need even more control over the component styling, use escape hatches.

Types Generated From Design Tokens

Some props (e.g. color, radii, container sizes, …) use types generated from design tokens. They restrict values to those defined in the design system and stay up to date with every token change.

If you need additional values, you’ll need to coordinate with a designer so they can be added to the Figma and become available through tokens.

Escape Hatches

While we encourage teams to utilize Spirit design as it is, we do realize that sometimes product specific customizations may be needed. In these cases, we encourage you or your designers to talk to us. We may be able to suggest an alternative implementation strategy, or perhaps your design can help propose future Spirit additions.

While the traditional className and style props are not supported in Spirit Web React components, there are two escape hatches that you can use at your own risk. These are UNSAFE_className and UNSAFE_style. Use of these props should be considered a last resort. They can be used to work around bugs or limitations in Spirit Web React, but should not be used in the long term.

The reasoning behind this is that future updates to Spirit design may cause unintended breaking changes in products. If the internal DOM structure or CSS properties of a Spirit Web React component change, this may lead to conflicts with CSS overrides in products. For this reason, className and style are unsafe, and if you use them know that you are doing so at your own risk.

Please consult additional styling with web package documentation.

Utilities

pxToRem()

pxToRem converts a pixel value to a rem string. It uses the Spirit base font size (16 px by default) and trims trailing decimal zeros.

import { pxToRem } from '@alma-oss/spirit-web-react/utils';

pxToRem(24); // → '1.5rem'
pxToRem('16px'); // → '1rem'
pxToRem(14, { baseFontSize: 16, decimals: 2 }); // → '0.88rem'

👉 See the units guide for full details, including the SCSS equivalent and how base font sizes relate to design tokens.

Controlled vs Uncontrolled Components

  • A Controlled Component is one that takes its current value through props and notifies changes through callbacks like onChange. A parent component "controls" it by handling the callback and managing its own state and passing the new values as props to the controlled component. You could also call this a "dumb component".

  • An Uncontrolled Component is one that stores its own state internally, and you query the DOM using a ref to find its current value when you need it. This is a bit more like traditional HTML.

All components are by default provided as controlled components so you must provide your own controlling or toggle functionality to make them work as you want.

For a better developer experience there is also an uncontrolled variant of the component provided. You can use the Uncontrolled variant for faster development.

Deprecations

This package uses the deprecation warnings for props, functions and components that will be removed or replaced in the next major release. Check your browser console to see if you are using any of the deprecated functionality.

Deprecations in the Browser's console

👉 See the DEPRECATIONS file for the list of all deprecations.

Warnings in Environments

Production

The warning utility which is used for deprecation warnings checks the process.env.NODE_ENV variable to determine if the warnings should be shown. If the environment variable is set to the production the warnings will not be shown.

Testing

While running tests, you likely will see the deprecation warnings. You can suppress the warnings by simply mocking the implementation of the warning utility or console.warn function. But we strongly discourage you from doing so, as the deprecation warnings are there to help you to prepare for the next major release.

Example for Jest:
beforeEach(() => {
  jest.spyOn(console, 'warn').mockImplementation(() => {});
});

afterEach(() => {
  console.warn.mockRestore();
});

Examples

👀 See examples for a live demo.

Implementing From Figma

If you are implementing Spirit components from Figma designs — with or without AI assistance — we provide and recommend the following tools to help you.

Figma Code Connect

Figma Code Connect links Figma components to code snippets. Use it to see how each design component is written in code and which props to use (e.g. size, iconName, textColor) when implementing. With Figma Dev Mode you can read the snippets directly in Figma in the "Code" tab.

Figma Code Connect

AI-Assisted Implementation

If you use AI-powered tools to implement UI from Figma, the following help the agent produce accurate Spirit code.

Figma MCP — Figma MCP (Model Context Protocol) lets the AI read your Figma files: design context, screenshots, and metadata for selected nodes. With the Figma desktop app open and the Figma MCP server configured in your tool, the agent can use tools such as get_design_context and get_screenshot. See Figma MCP documentation for more information.

Figma-to-code skill — Figma to Code (/spirit:figma-to-code) teaches an AI-powered tool how to implement production React from a Figma design using current Spirit Web React APIs. Use it when the requested output is React code. For a pre-handoff design-system review of a Figma frame, use /spirit:review-figma-design instead. Install the implementation skill with:

npx skills add https://github.com/alma-oss/spirit-design-system --skill figma-to-code

To learn more about skills, see Skills documentation.

Summary

  • If you use AI to review a Figma frame before handoff, run /spirit:review-figma-design first. That review is the quality gate for later implementation: unresolved design-system issues in the frame will carry into /spirit:figma-to-code output.
  • If you use AI to implement UI from Figma, install the Figma MCP and /spirit:figma-to-code, then prompt the agent with the Figma node or URL.
  • In Figma, use Figma Code Connect to see and copy the code for the component you are implementing.

License

See the LICENSE file for information.

版本与发布记录

@alma-oss/spirit-web-react 共发布 21 个版本,首次发布于 2025/12/15;过去 12 个月发布了 21 个版本。最新版本附带 npm provenance 构建来源证明,可追溯到公开的 CI 构建记录。

版本发布日期距今
5.5.0latest2026/10/1919 天前
5.4.02026/10/021 个月前
5.3.02026/09/022 个月前
5.2.12026/08/073 个月前
5.2.02026/07/293 个月前
5.1.02026/07/133 个月前

关于 @alma-oss/spirit-web-react 的常见问题

@alma-oss/spirit-web-react 是什么?

React implementation of Spirit Design System components

如何安装 @alma-oss/spirit-web-react?

在项目目录运行 npm install @alma-oss/spirit-web-react,或使用 pnpm add / yarn add / bun add。它还需要宿主项目提供 peer 依赖:react(^18.0.0 || ^19.0.0)、react-dom(^18.0.0 || ^19.0.0)。npm 7 及以上与 pnpm 8 及以上默认会自动安装必需的 peer 依赖,Yarn 需要手动安装。

@alma-oss/spirit-web-react 支持 TypeScript 吗?

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

@alma-oss/spirit-web-react 支持 ESM 还是 CommonJS?

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

@alma-oss/spirit-web-react 需要什么版本的 Node.js?

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

@alma-oss/spirit-web-react 可以商用吗?

@alma-oss/spirit-web-react 使用 MIT 许可。宽松许可:允许商业使用、修改和再分发,通常只需在分发时保留原版权与许可声明。以上为概括,不构成法律意见,请以许可原文为准。

@alma-oss/spirit-web-react 的下载量有多少?

根据 npm 官方下载统计,@alma-oss/spirit-web-react 在 2026/10/06 – 2026/11/04 这 30 天内被下载 3,369 次,日均约 112 次。下载量会把 CI 构建、镜像同步和作为其他包依赖的安装一并计入,适合横向比较热度,不能直接等同于使用人数。

@alma-oss/spirit-web-react 还在维护吗?

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