Shared React components library for Element Web, Aurora, Element modules... This package provides opinionated UI components built on top of the Compound Design System and Compound Web. This is not a design system by itself, but rather a set of larger components.
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 as a peer
dependency:
pnpm add @element-hq/web-shared-components
pnpm add @vector-im/compound-web(This avoids problems where we end up with different versions of compound-web in the top-level project and web-shared-components).
Both JavaScript and CSS can be imported as follows:
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:
@import url("@element-hq/web-shared-components");There are two kinds of components in this library:
- regular react component which doesn't follow specific pattern.
- view component(MVVM pattern).
Tip
These components are available in the project storybook.
These components can be used directly by passing props. Example:
import { Flex } from "@element-hq/web-shared-components";
function MyApp() {
return <Flex align="center" />;
}These components follow the MVVM pattern. A ViewModel instance should be provided as a prop.
Here's a basic example:
import { ViewExample } from "@element-hq/web-shared-components";
function MyApp() {
const viewModel = new ViewModelExample();
return <ViewExample vm={viewModel} />;
}useI18n()- Hook for translationsI18nApi- Internationalization API utilities
DateUtils- Date formatting and manipulationhumanize- Human-readable time formatting
FormattingUtils- Text and data formatting utilitiesnumbers- Number formatting utilities
- Node.js >= 20.0.0
- pnpm => 10
# Install dependencies
pnpm install
# Build the library
pnpm prepackpnpm storybookMost components should be written as MVVM pattern view components. See existing components for examples. The exceptions are low level components that don't need a view model.
All components should have accompanying Storybook stories for documentation and visual testing. Stories are written in TypeScript using the Component Story Format (CSF).
Use shallow, browse-oriented story titles such as RoomList/RoomListSearchView or TimelineBody/DecryptionFailureBodyView. Do not mirror the full source path in the Storybook title.
Place the story file next to the component with the .stories.tsx extension:
MyComponent/
├── MyComponent.tsx
├── MyComponent.module.css
└── MyComponent.stories.tsx
For regular React components (non-MVVM), create stories by defining a meta object and story variations:
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,
},
};For MVVM components, create a wrapper component that uses useMockedViewModel and withViewDocs:
import React, { type JSX } from "react";
import { fn } from "storybook/test";
import type { Meta, StoryObj } from "@storybook/react-vite";
import { MyComponentView, type MyComponentViewSnapshot, type MyComponentViewActions } from "./MyComponentView";
import { useMockedViewModel } from "../../viewmodel";
import { withViewDocs } from "../../../.storybook/withViewDocs";
// Combine snapshot and actions for easier typing
type MyComponentProps = MyComponentViewSnapshot & MyComponentViewActions;
// 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 => {
const vm = useMockedViewModel(rest, {
onAction,
});
return <MyComponentView vm={vm} />;
};
// withViewDocs copies the View's JSDoc description onto the wrapper for Storybook autodocs
const MyComponentViewWrapper = withViewDocs(MyComponentViewWrapperImpl, MyComponentView);
// Must use `satisfies` (not `as` or `: Meta`) to preserve type info for docgen
const meta = {
title: "Category/MyComponentView",
component: MyComponentViewWrapper,
tags: ["autodocs"],
args: {
// Snapshot properties (state)
title: "Default Title",
isLoading: false,
// Action properties (callbacks)
onAction: fn(),
},
} satisfies Meta<typeof MyComponentViewWrapper>;
export default meta;
type Story = StoryObj<typeof MyComponentViewWrapper>;
export const Default: Story = {};
export const Loading: Story = {
args: {
isLoading: true,
},
};Thanks to this approach, we can directly use primitives in the story arguments instead of a view model object.
Important
Three requirements must be met for snapshot field documentation to appear in Storybook's ArgTypes table:
- Named wrapper variable — the wrapper must be assigned to a named
const(e.g.MyComponentViewWrapperImpl) before being passed towithViewDocs, so thatreact-docgen-typescriptcan extract its props. withViewDocscall — wraps the wrapper component with the original View to copy the View's JSDoc description.satisfies Meta— the meta object must usesatisfies Meta<...>(notas Meta<...>or: Meta<...> =). Type assertions and annotations erase the inferred component type that docgen relies on.
This package uses @storybook/addon-designs to embed Figma designs directly in Storybook. This helps developers compare their implementation with the design specs.
- Get the Figma URL: Open your design in Figma, click "Share" → "Copy link"
- Add to story parameters: Include the
designobject in the meta'sparameters - 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
- File links:
Example with Figma integration:
const meta = {
title: "RoomList/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",
},
},
} satisfies Meta<typeof RoomListSearchViewWrapper>;
export default meta;The Figma design will appear in the "Design" tab in Storybook.
For utility functions, helpers, and other non-UI exports, create documentation stories using TSX format with TypeDoc-generated markdown.
src/core/utils/humanize.stories.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: "Core/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:
- Write TsDoc in your utility function
- Export the function from
src/index.ts - Run
pnpm build:docto generate TypeDoc markdown - Create a
.stories.tsxfile importing the generated markdown - The documentation appears automatically in Storybook
Two types of tests are available: unit tests and visual regression tests.
These tests cover the logic of the components and utilities. Built with Vitest and React Testing Library.
pnpm test:unitThese tests ensure the UI components render correctly. Built with Storybook and run under vitest using playwright.
pnpm test:storybook:updateEach 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.
Screenshots are located in packages/shared-components/__vis__/.
Important
In case of docker issues with Playwright, see playwright EW documentation.
First see our translation guide and translation dev guide. To generate translation strings for this package, run:
pnpm i18nTwo steps are required to publish a new version of this package:
- Bump the version in
package.jsonfollowing semver rules and open a PR. - Once merged run the github workflow