Product-neutral UI components and design tokens shared across Lablup products.
Consumers are Lablup product frontends, including all-smi. The package holds presentation only. It has no API client, no application state, no router, and no desktop-shell integration, because those differ per product and are what makes a component impossible to share.
pnpm add @lablup/ui-common
That is npmjs, which needs no authentication and is the right route for essentially everyone, including open-source consumers and forked CI.
react and react-dom are peer dependencies. Version 18 and 19 are both
supported.
The same versions are also published to GitHub Packages for projects that already authenticate to GitHub. It is a mirror, not a different package, so there is no reason to prefer it unless your organization requires it.
GitHub Packages requires authentication even for public packages, which is why it is not the default route here. To use it, point the scope at that registry:
# .npmrc
@lablup:registry=https://npm.pkg.github.com
In GitHub Actions, authenticate with the built-in token and grant
permissions: packages: read:
- uses: actions/setup-node@v5
with:
registry-url: https://npm.pkg.github.com
scope: "@lablup"
env:
NODE_AUTH_TOKEN: ${{ secrets.GITHUB_TOKEN }}Locally, use a personal access token with read:packages, in your user
~/.npmrc and never in a project file.
Import from the root, or from a component subpath when you want the smallest possible graph:
import { Button, StatusTag } from "@lablup/ui-common";
import { Drawer } from "@lablup/ui-common/components/Drawer";Styling is opt-in and split so that importing a component never drags in every theme:
// The token contract the components resolve against. Required.
// It also carries the default palette, so this alone is a working theme.
import "@lablup/ui-common/styles/base.css";
// Optional, and only if you switch themes at runtime through [data-theme].
import "@lablup/ui-common/styles/themes/orange-dark.css";The package ships the theming mechanism and one default palette, the Lablup
brand orange. A product with its own visual identity defines its own
[data-theme] blocks over the same 113 token names and ships them itself,
rather than the package accumulating everyone's palettes. The source product
does exactly that with its five families.
Component CSS travels with the component: importing Button brings
Button.css with it, so a subpath import pulls that component's styles and no
others. The two entry points above are the only stylesheets you import by hand,
and base.css is the one you must not skip, since it carries the tokens every
component resolves against.
No component calls a translation function or reads a locale key. Every user-facing string is a prop with an English default:
<Drawer isOpen={open} onClose={close} closeLabel={t("common.closeDrawer")}>
...
</Drawer>A consumer with no i18n setup gets working, accessible English. A consumer with translations passes them in. Neither ends up depending on the other's locale bundle.
Components resolve their colors, spacing, motion, radii, and shadows from
--token-* custom properties. Those properties are a versioned part of the
public surface: renaming or removing one is a breaking change, exactly like
renaming a prop. Every token a component reads has a fallback, so a consumer
that adopts a component without adopting a theme still renders.
The package is not a home for every reusable-looking component. A component is admitted when all three hold:
- It is product-neutral, taking generic view models and callbacks rather than any product's API types.
- It has a concrete consumer in more than one product, present or committed.
- It brings its accessibility and behavior tests with it.
Anything that fails one of these stays with the product that needs it. See CONTRIBUTING.md.
- API clients, endpoints, and authentication.
- Application state, stores, and routing.
- Tauri APIs and plugins, and any desktop-shell assumption.
- Product locale keys and any application-global i18n instance.
- Anything from the private AI companion package. The dependency runs the other way, and CI fails if it ever reverses.
pnpm install
pnpm run verify # typecheck, lint, format, boundary, test, build, pack
pnpm run test:watch
pnpm run verify is what CI runs. It ends by packing the real tarball and
asserting that every path in the exports map resolves inside it, then a
separate job installs that tarball into a clean external React project under
fixture/. Building green and being installable are different claims, and the
second is the one consumers depend on.
The initial component and token slice was extracted from an existing Lablup product frontend. The import is clean: no upstream git history was published here. The detailed migration record, including the source commit for each imported file, lives in that product's own repository.
Apache-2.0. See NOTICE.