@bitforgehq/ui
v0.3.0
Published
Bitforge UI: Angular 22 widgets (Meadow Sage design, Tailwind v4, signals-first) and, from v0.2, a Capacitor app shell.
Readme
@bitforgehq/ui
Bitforge's shared Angular component library: Meadow Sage design, Tailwind CSS v4, signals-first, Angular 22.
- Widgets:
@bitforgehq/ui - Number field:
@bitforgehq/ui/number-input(bf-number-input; loads Angular Forms, peer dependency@angular/forms) - Icons:
@bitforgehq/ui/icons(Lucide data, peer dependencylucide) - Overlay core:
@bitforgehq/ui/overlay(no Capacitor; peer dependencies@angular/cdkandrxjs) - Mobile shell:
@bitforgehq/ui/mobile(from v0.2) - Test support:
@bitforgehq/ui/testing(jsdom shims,provideBfTesting(), CDK component harnesses; needs@angular/cdkas a dev dependency)
Install, theming and every component's API are documented for humans and agents in ai/UI_GUIDE.md and ai/components/*.md inside this package, and at the docs site (https://bitforge-ui.web.app).
Install
1. Check the prerequisites
Node 22.22.3 or later in the 22 line, 24.15 or later in the 24 line, or 26 and later (the Angular CLI 22 range ^22.22.3 || ^24.15.0 || >=26.0.0; it refuses older versions and warns on odd releases such as 23 and 25), Bun 1.3 or later and the Angular CLI 22.
node --version # v22.22.3+, v24.15+ or v26+
bun --version # 1.3 or later
bun add --global @angular/cli@222. Create the app
A new Angular 22 app with plain CSS styles. Skip this step in an existing app.
ng new my-app --style css --package-manager bun
cd my-app3. Add Tailwind CSS
The theme is a Tailwind CSS 4 theme. ng add tailwindcss installs Tailwind and its PostCSS plugin and writes .postcssrc.json.
ng add tailwindcss4. Add the packages
lucide provides the icon data for bf-icon; the two @fontsource packages provide the Manrope and JetBrains Mono fonts.
bun add @bitforgehq/ui lucide @fontsource/manrope @fontsource/jetbrains-mono5. Wire the theme into styles.css
Replace the contents of src/styles.css. Keep tailwindcss first: the theme redefines the palette its utilities read, and registers the package as a Tailwind source, so no tailwind.config.js is needed.
@import 'tailwindcss';
@import '@bitforgehq/ui/theme.css';
@import '@fontsource/manrope/500.css';
@import '@fontsource/manrope/600.css';
@import '@fontsource/manrope/700.css';
@import '@fontsource/manrope/800.css';
@import '@fontsource/jetbrains-mono/400.css';6. Theme the page
In src/index.html, give <body> the theme background and text colour, so the page itself follows light and dark mode.
<body class="bg-bg text-text">
<app-root></app-root>
</body>7. Switch to dark mode
Add the dark class to <html> (and remove it for light mode). Every token is redefined under .dark, and the theme sets color-scheme, so scrollbars and native controls follow too. Components need no dark: classes.
<html lang="en" class="dark">
<!-- head and body stay as they are -->
</html>8. Use a first component
Import a widget from @bitforgehq/ui into the component that uses it. Replace src/app/app.ts with the code below and delete the welcome page it no longer uses, app.html and app.css. app.spec.ts tests that welcome page too: delete its should render title case (or make it check the button), so ng test keeps passing.
import { Component } from '@angular/core';
import { BfButtonComponent } from '@bitforgehq/ui';
@Component({
selector: 'app-root',
imports: [BfButtonComponent],
template: `<bf-button variant="primary">Save</bf-button>`,
})
export class App {}9. Check it
Run the app and open it: you should now see an indigo, pill-shaped Save button in Manrope on the theme background. With class="dark" on <html>, the whole page is dark.
ng servePeer dependencies
@bitforgehq/ui (the widgets entry) needs @angular/common, @angular/core, @angular/forms
and lucide (for @bitforgehq/ui/icons and /mobile); these are required peers. The widgets
entry loads no @angular/forms code at runtime (its form widgets only implement the Signal Forms
interfaces); @bitforgehq/ui/number-input does, since bf-number-input parses through Angular's
transformedValue(), so @angular/forms is required for it. The overlay entry
@bitforgehq/ui/overlay (the Capacitor-free overlay core) additionally needs @angular/cdk and
rxjs (a CDK peer). The mobile entry @bitforgehq/ui/mobile needs those plus @angular/router
and @capacitor/core. They are declared optional so a desktop-only install stays free of them:
an app that imports /overlay or /mobile installs them itself. The Capacitor plugins @capacitor/app,
@capacitor/keyboard and @capacitor/haptics are optional peers too; install only the ones your
app uses, and /mobile builds and runs without the others.
Compatibility
@bitforgehq/ui 0.x is client-rendered only. SSR, hydration and prerendering of pages that render
its components are not supported or tested. Component ids come from a module-level counter and can
differ between server and client, and the shell's theme is read from localStorage on the client.
Render library components only in the browser, for example in a @defer block or a client-only
route. Support will be decided before 1.0.
The one server-side check is a render smoke, not a support test: the library repository's
tools/ssr-smoke.ts renders 28 fixtures with renderApplication() (every root widget, bf-icon,
bf-number-input, a /mobile page with provideBfShell(), bf-page, bf-header and
bf-content, bf-tabs with bf-stack-outlet under the router, and an /overlay dialog), and all
of them render without an error. 19 of them render identically twice. The other 9 number their ids
differently on every render, from a module-level counter, so they could not hydrate: bf-checkbox,
bf-input, bf-radio-group, bf-segmented, bf-select, bf-textarea, bf-toggle,
bf-number-input and the /overlay dialog. Hydration itself is still not supported or tested, and
more stands in its way: /mobile writes to <html> and adds a keyboard probe to <body>, and an
/overlay dialog renders outside the app root and marks it inert.
Testing
@bitforgehq/ui/testing is for your specs only (Vitest, the Angular CLI default, or Jest) and needs
@angular/cdk as a dev dependency (bun add -d @angular/cdk) even in an app that never opens an
overlay. Call installBfJsdomShims() in the runner's setup file (the setupFiles of the test
target in angular.json): it adds the Web Animations API, PointerEvent and matchMedia where
jsdom lacks them. provideBfTesting() passes defaults and labels to the widgets and cleans up what
overlays and themes leave on the document after each test (when TestBed destroys the test
environment, so keep its default teardown: { destroyAfterEach: true }), and the CDK harnesses
(BfSelectHarness, BfRadioGroupHarness, BfSegmentedHarness, BfDialogHarness,
BfMenuButtonHarness, BfMenuHarness, BfMenuItemHarness, BfDesktopToastHarness) drive the
widgets whose DOM a spec should not query by hand; overlays come from
TestbedHarnessEnvironment.documentRootLoader(fixture). An app on the mobile shell uses
provideBfShellTesting() from @bitforgehq/ui/mobile instead (router, shell, an in-memory theme
store and the same cleanup), so /testing itself never loads Capacitor.
Density and labels
Every field and control shares one height scale (BfSize: sm 32, md 40, lg 48 px) with a
compact md default for desktop web apps. Set another step once with provideBfDefaults() in
ApplicationConfig.providers ({ size: 'sm' } for a data-dense app, { fieldSize: 'lg' } for a
Capacitor app, because under touch the actions and choice controls reach 44 px through an invisible
extension but text fields get none, so only the fields need the 48 px step) or in a component's
providers for one part of the app, such as a dense toolbar.
provideBfLabels() replaces every built-in English string, including the default error text
shown for a validator without a message, the same two ways. The docs site's Density and Labels
guides list the scale and every string.
