2026-01-14 15:13:09 +01:00
# @element-hq/web-shared-components
2026-02-09 16:26:21 +01:00
[Online storybook ](https://shared-components-storybook.element.dev )
2026-01-16 18:13:57 +00:00
Shared React components library for Element Web, Aurora, Element
modules... This package provides opinionated UI components built on top of the
[Compound Design System ](https://compound.element.io ) and [Compound
Web ](https://github.com/element-hq/compound-web ). This is not a design system
by itself, but rather a set of larger components.
2026-01-14 15:13:09 +01:00
2026-01-16 18:13:57 +00:00
## Installation in a new project
2026-01-14 15:13:09 +01:00
2026-01-16 18:13:57 +00:00
When adding this library to a new project, as well as installing
`@element-hq/web-shared-components` as normal, you will also need to add
[compound-web ](https://github.com/element-hq/compound-web ) as a peer
dependency:
2026-01-14 15:13:09 +01:00
``` bash
2026-02-11 10:35:29 +00:00
pnpm add @element-hq/web-shared-components
pnpm add @vector-im/compound-web
2026-01-14 15:13:09 +01:00
```
2026-01-16 18:13:57 +00:00
(This avoids problems where we end up with different versions of compound-web in the
top-level project and web-shared-components).
2026-01-14 15:13:09 +01:00
## Usage
### Basic Import
Both JavaScript and CSS can be imported as follows:
``` javascript
import { RoomListHeaderView , useViewModel } from "@element-hq/web-shared-components" ;
import "@element-hq/web-shared-components/dist/element-web-shared-components.css" ;
```
or in CSS file:
``` css
@ import url ( "@element-hq/web-shared-components" ) ;
```
### Using Components
2026-01-16 18:13:57 +00:00
There are two kinds of components in this library:
2026-01-14 15:13:09 +01:00
- _regular_ react component which doesn't follow specific pattern.
- _view_ component(MVVM pattern).
> [!TIP]
2026-01-16 18:13:57 +00:00
> These components are available in the project storybook.
2026-01-14 15:13:09 +01:00
#### Regular Components
These components can be used directly by passing props. Example:
``` tsx
import { Flex } from "@element-hq/web-shared-components" ;
function MyApp() {
return < Flex align = "center" / > ;
}
```
#### View (MVVM) Components
2026-01-16 18:13:57 +00:00
These components follow the [MVVM pattern ](../../docs/MVVM.md ). A ViewModel
instance should be provided as a prop.
2026-01-14 15:13:09 +01:00
Here's a basic example:
2026-02-23 17:03:49 +01:00
``` tsx
2026-01-14 15:13:09 +01:00
import { ViewExample } from "@element-hq/web-shared-components" ;
function MyApp() {
const viewModel = new ViewModelExample ( ) ;
return < ViewExample vm = { viewModel } / > ;
}
```
### Utilities
#### Internationalization
- `useI18n()` - Hook for translations
- `I18nApi` - Internationalization API utilities
#### Date & Time
- `DateUtils` - Date formatting and manipulation
- `humanize` - Human-readable time formatting
#### Formatting
- `FormattingUtils` - Text and data formatting utilities
- `numbers` - Number formatting utilities
## Development
### Prerequisites
- Node.js >= 20.0.0
2026-02-11 10:35:29 +00:00
- pnpm => 10
2026-01-14 15:13:09 +01:00
### Setup
``` bash
# Install dependencies
2026-02-11 10:35:29 +00:00
pnpm install
2026-01-14 15:13:09 +01:00
# Build the library
2026-03-10 11:22:20 +00:00
pnpm prepack
2026-01-14 15:13:09 +01:00
```
### Running Storybook
``` bash
2026-02-11 10:35:29 +00:00
pnpm storybook
2026-01-14 15:13:09 +01:00
```
### Write components
2026-01-16 18:13:57 +00:00
Most components should be written as [MVVM pattern ](../../docs/MVVM.md ) view
components. See existing components for examples. The exceptions are low level
components that don't need a view model.
2026-01-14 15:13:09 +01:00
2026-01-21 14:31:52 +01:00
### Write Storybook Stories
All components should have accompanying Storybook stories for documentation and visual testing. Stories are written in TypeScript using the [Component Story Format (CSF) ](https://storybook.js.org/docs/api/csf ).
#### Story File Structure
Place the story file next to the component with the `.stories.tsx` extension:
```
MyComponent/
├── MyComponent.tsx
├── MyComponent.module.css
└── MyComponent.stories.tsx
```
#### Regular Component Stories
For regular React components (non-MVVM), create stories by defining a meta object and story variations:
``` tsx
import type { Meta , StoryObj } from "@storybook/react-vite" ;
import { fn } from "storybook/test" ;
import { MyComponent } from "./MyComponent" ;
const meta = {
title : "Category/MyComponent" ,
component : MyComponent ,
tags : [ "autodocs" ] ,
args : {
// Default args for all stories
label : "Default Label" ,
onClick : fn ( ) , // Mock function for tracking interactions
} ,
} satisfies Meta < typeof MyComponent > ;
export default meta ;
type Story = StoryObj < typeof meta > ;
// Default story uses the default args
export const Default : Story = { } ;
// Override specific args for variations
export const WithCustomLabel : Story = {
args : {
label : "Custom Label" ,
} ,
} ;
export const Disabled : Story = {
args : {
disabled : true ,
} ,
} ;
```
#### MVVM Component Stories
2026-02-23 17:03:49 +01:00
For MVVM components, create a wrapper component that uses `useMockedViewModel` and `withViewDocs` :
2026-01-21 14:31:52 +01:00
``` tsx
import React , { type JSX } from "react" ;
import { fn } from "storybook/test" ;
2026-02-23 17:03:49 +01:00
import type { Meta , StoryObj } from "@storybook/react-vite" ;
2026-01-21 14:31:52 +01:00
import { MyComponentView , type MyComponentViewSnapshot , type MyComponentViewActions } from "./MyComponentView" ;
2026-02-23 17:03:49 +01:00
import { useMockedViewModel } from "../../viewmodel" ;
import { withViewDocs } from "../../../.storybook/withViewDocs" ;
2026-01-21 14:31:52 +01:00
// Combine snapshot and actions for easier typing
type MyComponentProps = MyComponentViewSnapshot & MyComponentViewActions ;
2026-02-23 17:03:49 +01:00
// Wrapper component that creates a mocked ViewModel.
// Must be a named variable (not inline) for docgen to extract its props.
const MyComponentViewWrapperImpl = ( { onAction , . . . rest } : MyComponentProps ) : JSX . Element = > {
2026-01-21 14:31:52 +01:00
const vm = useMockedViewModel ( rest , {
onAction ,
} ) ;
return < MyComponentView vm = { vm } / > ;
} ;
2026-02-23 17:03:49 +01:00
// withViewDocs copies the View's JSDoc description onto the wrapper for Storybook autodocs
const MyComponentViewWrapper = withViewDocs ( MyComponentViewWrapperImpl , MyComponentView ) ;
2026-01-21 14:31:52 +01:00
2026-02-23 17:03:49 +01:00
// Must use `satisfies` (not `as` or `: Meta`) to preserve type info for docgen
const meta = {
2026-01-21 14:31:52 +01:00
title : "Category/MyComponentView" ,
component : MyComponentViewWrapper ,
tags : [ "autodocs" ] ,
args : {
// Snapshot properties (state)
title : "Default Title" ,
isLoading : false ,
// Action properties (callbacks)
onAction : fn ( ) ,
} ,
2026-02-23 17:03:49 +01:00
} satisfies Meta < typeof MyComponentViewWrapper > ;
2026-01-21 14:31:52 +01:00
2026-02-23 17:03:49 +01:00
export default meta ;
type Story = StoryObj < typeof MyComponentViewWrapper > ;
2026-01-21 14:31:52 +01:00
2026-02-23 17:03:49 +01:00
export const Default : Story = { } ;
2026-01-21 14:31:52 +01:00
2026-02-23 17:03:49 +01:00
export const Loading : Story = {
args : {
isLoading : true ,
} ,
2026-01-21 14:31:52 +01:00
} ;
```
Thanks to this approach, we can directly use primitives in the story arguments instead of a view model object.
2026-02-23 17:03:49 +01:00
> [!IMPORTANT]
> Three requirements must be met for snapshot field documentation to appear in Storybook's ArgTypes table:
>
> 1. **Named wrapper variable** — the wrapper must be assigned to a named `const` (e.g. `MyComponentViewWrapperImpl`) before being passed to `withViewDocs`, so that `react-docgen-typescript` can extract its props.
> 2. **`withViewDocs` call** — wraps the wrapper component with the original View to copy the View's JSDoc description.
> 3. **`satisfies Meta`** — the meta object must use `satisfies Meta<...>` (not `as Meta<...>` or `: Meta<...> =`). Type assertions and annotations erase the inferred component type that docgen relies on.
2026-01-21 14:31:52 +01:00
#### Linking Figma Designs
This package uses [@storybook/addon-designs ](https://github.com/storybookjs/addon-designs ) to embed Figma designs directly in Storybook. This helps developers compare their implementation with the design specs.
1. **Get the Figma URL ** : Open your design in Figma, click "Share" → "Copy link"
2. **Add to story parameters ** : Include the `design` object in the meta's `parameters`
3. **Supported URL formats ** :
- File links: `https://www.figma.com/file/...`
- Design links: `https://www.figma.com/design/...`
- Specific node: `https://www.figma.com/design/...?node-id=123-456`
Example with Figma integration:
``` tsx
2026-02-23 17:03:49 +01:00
const meta = {
2026-01-21 14:31:52 +01:00
title : "Room List/RoomListSearchView" ,
component : RoomListSearchViewWrapper ,
tags : [ "autodocs" ] ,
args : {
// ... your args
} ,
parameters : {
design : {
type : "figma" ,
url : "https://www.figma.com/design/vlmt46QDdE4dgXDiyBJXqp/ER-33-Left-Panel?node-id=98-1979" ,
} ,
} ,
2026-02-23 17:03:49 +01:00
} satisfies Meta < typeof RoomListSearchViewWrapper > ;
export default meta ;
2026-01-21 14:31:52 +01:00
```
The Figma design will appear in the "Design" tab in Storybook.
2026-02-06 11:01:42 +01:00
#### Non-UI Utility Stories
For utility functions, helpers, and other non-UI exports, create documentation stories using TSX format with TypeDoc-generated markdown.
`src/utils/humanize.stories.tsx`
``` tsx
import React from "react" ;
import { Markdown } from "@storybook/addon-docs/blocks" ;
import type { Meta } from "@storybook/react-vite" ;
import humanizeTimeDoc from "../../typedoc/functions/humanizeTime.md?raw" ;
const meta = {
title : "utils/humanize" ,
parameters : {
docs : {
page : ( ) = > (
< >
< h1 > humanize < / h1 >
< Markdown > { humanizeTimeDoc } < / Markdown >
< / >
) ,
} ,
} ,
tags : [ "autodocs" , "skip-test" ] ,
} satisfies Meta ;
export default meta ;
// Docs-only story - renders nothing but triggers autodocs
export const Docs = {
render : ( ) = > null ,
} ;
```
> [!NOTE]
> Be sure to include the `skip-test` tag in your utility stories to prevent them from running as visual tests.
**Workflow: **
1. Write TsDoc in your utility function
2. Export the function from `src/index.ts`
2026-02-11 10:35:29 +00:00
3. Run `pnpm build:doc` to generate TypeDoc markdown
2026-02-06 11:01:42 +01:00
4. Create a `.stories.tsx` file importing the generated markdown
5. The documentation appears automatically in Storybook
2026-01-14 15:13:09 +01:00
### Tests
Two types of tests are available: unit tests and visual regression tests.
### Unit Tests
2026-01-22 14:17:36 +00:00
These tests cover the logic of the components and utilities. Built with Vitest
2026-01-16 18:13:57 +00:00
and React Testing Library.
2026-01-14 15:13:09 +01:00
``` bash
2026-02-11 10:35:29 +00:00
pnpm test:unit
2026-01-14 15:13:09 +01:00
```
### Visual Regression Tests
2026-01-22 14:17:36 +00:00
These tests ensure the UI components render correctly.
Built with Storybook and run under vitest using playwright.
2026-01-14 15:13:09 +01:00
``` bash
2026-02-11 10:35:29 +00:00
pnpm test:storybook:update
2026-01-14 15:13:09 +01:00
```
2026-01-16 18:13:57 +00:00
Each story will be rendered and a screenshot will be taken and compared to the
existing baseline. If there are visual changes or AXE violation, the test will
fail.
2026-01-14 15:13:09 +01:00
2026-01-27 14:31:14 +01:00
Screenshots are located in `packages/shared-components/__vis__/` .
> [!IMPORTANT]
> In case of docker issues with Playwright, see [playwright EW documentation](https://github.com/element-hq/element-web/blob/develop/docs/playwright.md#supported-container-runtimes).
2026-01-14 15:13:09 +01:00
### Translations
2026-02-06 11:01:42 +01:00
First see our [translation guide ](../../docs/translating.md ) and [translation dev guide ](../../docs/translating-dev.md ).
2026-01-14 15:13:09 +01:00
To generate translation strings for this package, run:
``` bash
2026-02-11 10:35:29 +00:00
pnpm i18n
2026-01-14 15:13:09 +01:00
```
2026-01-26 16:47:24 +01:00
## Publish a new version
Two steps are required to publish a new version of this package:
1. Bump the version in `package.json` following semver rules and open a PR.
2026-04-10 15:37:45 +01:00
2. Once merged run the [github workflow ](https://github.com/element-hq/element-web/actions/workflows/npm-publish.yaml )