Angular 21StandaloneMITZero NgModules
Directives over native elements. Tokens over hardcoded styles.
Bursit Angular is an Angular 21 library of standalone directives that extend
<button>, <input> and their siblings. Native semantics
stay intact, and every visual value still resolves through one token layer.
palette
--color-*
space
--space-*
radius
--radius-*
01 Philosophy
Three decisions that shape every component
Bursit Angular does very little on its own. Everything it does follows from three constraints that are enforced across the whole API.
-
01
Directive-first
The primitives are directives over native elements.
<button bursitButton>stays a real button and<input bursitInput>stays a real input, so native accessibility, keyboard behaviour and form participation are preserved instead of reimplemented.<button bursitButton>
-
02
Tokens own the visuals
Every color, space, radius and shadow resolves to a
var()frombursit-ui-tokens. The library ships structure only, which is why the same components can be re-skinned without touching a line of component code.var(--color-primary)
-
03
Standalone by default
There is no
NgModuleanywhere in the package. Add the primitive you need to your component imports array and use it — nothing else registers itself in your application.imports: [FormField, InputDirective]
02 Design tokens
The design system lives outside your components
Colors, type, space, radii and shadows are CSS custom properties published by
bursit-ui-tokens. Nothing below is a screenshot — every sample on this page
resolves through var().
Brand
The accent every interactive element resolves to.
-
--color-primary -
--color-primary-hover -
--color-primary-active -
--color-primary-subtle -
--color-primary-contrast
Secondary
The cool counterpart, used for support accents and glows.
-
--color-secondary -
--color-secondary-hover -
--color-secondary-active -
--color-secondary-subtle -
--color-secondary-contrast
Semantic
Each status color also ships -hover, -active, -contrast and alpha partners.
-
--color-success -
--color-warning -
--color-error -
--color-info
Neutral ramp
0 is always the surface end and 1000 the ink end, so the whole ramp inverts when the theme flips. Every step is a token.
- 0
- 50
- 100
- 200
- 300
- 400
- 500
- 600
- 700
- 800
- 900
- 950
- 1000
Typography scale
Nine steps on a single ramp, from --font-size-xs to
--font-size-5xl.
-
--font-size-xsDirective-first -
--font-size-smDirective-first -
--font-size-baseDirective-first -
--font-size-lgDirective-first -
--font-size-xlDirective-first -
--font-size-2xlDirective-first -
--font-size-3xlDirective-first -
--font-size-4xlDirective-first -
--font-size-5xlDirective-first
Spacing scale
Nine steps used for padding, gap and rhythm. Each bar is exactly as wide as its token.
-
--space-px -
--space-xs -
--space-sm -
--space-md -
--space-lg -
--space-xl -
--space-2xl -
--space-3xl -
--space-4xl
Rebranding is one declaration
Components never reference a value directly, so overriding a token is enough to re-skin every primitive that consumes it — including the ones you have not written yet.
The same mechanism is what makes theming work: light and dark are two sets of token values behind the same names.
/* Rebrand the whole library from one place. */
:root {
--color-primary: #your-brand;
--radius-md: 0.75rem;
--font-family-sans: 'Your Sans', system-ui, sans-serif;
}
03 Components
The complete public surface
Six directives, nine components and three services. Everything is standalone, everything is tree-shakeable, and the selector on each card is exactly what you write in your template.
-
ButtonDirective
DirectiveApplies button styling to native elements. The color input selects primary, secondary, outline, link or danger.
-
[bursitButton]
-
-
InputDirective
DirectiveReactive states for input and textarea — focus, hover, invalid, disabled and an optional floating label, driven by NgControl.
-
[bursitInput]
-
-
LabelDirective
DirectiveLabel that binds its for attribute to the field id published by FormField.
-
[bursitLabel]
-
-
TooltipDirective
DirectiveOverlay tooltip bound to focus and hover, with configurable position, show and hide delays, and an optional arrow.
-
[bursitTooltip]
-
-
Error
DirectiveError slot for a field. Renders with role="alert" and takes part in the control’s described-by chain.
-
[bursitError]
-
-
Modal slots
DirectiveNamed content slots projected into the modal chrome.
-
[bursitModalHeader] -
[bursitModalBody] -
[bursitModalFooter]
-
-
FormField
ComponentGroups label, control, error and helper text. Generates the field id and exposes group semantics.
-
<bursit-form-field>
-
-
Select
ComponentSelect built on CDK Overlay with full keyboard support and a ControlValueAccessor.
-
<bursit-select>
-
-
Option
ComponentA single option inside bursit-select. Its label is read from the projected text.
-
<bursit-option>
-
-
Checkbox
ComponentChecked, indeterminate and disabled states, wired into Angular forms.
-
<bursit-checkbox>
-
-
Message
ComponentHelper text slot, linked to the control it describes.
-
<bursit-message>
-
-
Avatar
ComponentImage avatar that falls back to initials computed from userName, in six sizes from xs to 2xl.
-
<bursit-avatar>
-
-
Icon
ComponentLucide-backed icon with name, size, color and strokeWidth inputs. Inherits currentColor by default.
-
<bursit-icon>
-
-
Modal
ComponentDialog chrome with a focus trap and three named slots. Opened through ModalService.
-
<bursit-modal>
-
-
Toast
ComponentToast overlay and a single toast item, with success, info, warning and error variants.
-
<bursit-toast-container> -
<bursit-toast-item>
-
-
BursitThemeService
ServiceLight, dark and system themes. Applies the bursit-theme attribute to <html> and persists the choice.
-
inject(BursitThemeService)
-
-
ToastService
ServiceImperative toasts. show(options) returns a ToastRef with dismiss() and an afterClosed() stream.
-
inject(ToastService)
-
-
ModalService
ServiceOpens a component in a CDK overlay and returns a ModalRef with close(), dismiss() and afterClosed().
-
inject(ModalService)
-
04 Getting started
From install to a working field
No providers to register, no module to configure. The schematic wires the token layer into your workspace and the primitives are imported like any other standalone declaration.
-
01
Install and wire the tokens
The schematic installs the package and appends the
bursit-ui-tokensstylesheet to thestylesarray of every project inangular.json.ng add bursit-angular -
02
Import the primitives you use
A field is a container, a label, a control and an error slot. Native elements keep their own semantics — the directives only add state and styling.
import { Component } from '@angular/core'; import { FormControl, ReactiveFormsModule } from '@angular/forms'; import { ErrorComponent, FormField, InputDirective, LabelDirective } from 'bursit-angular'; @Component({ selector: 'app-account', imports: [ReactiveFormsModule, FormField, InputDirective, LabelDirective, ErrorComponent], template: ` <bursit-form-field> <label bursitLabel>Work email</label> <input bursitInput type="email" [formControl]="email" required /> <span bursitError>Enter a valid work email address</span> </bursit-form-field> `, }) export class AccountComponent { readonly email = new FormControl('', { nonNullable: true }); } -
03
Check it against the live examples
Every primitive has a story with its full input surface, states and edge cases. That is also where the theme switching is easiest to see.
05 Theming
One attribute switches the whole interface
BursitThemeService resolves the requested mode and writes it to the
bursit-theme attribute on the document element. Because every component reads
tokens rather than values, that single attribute repaints everything.
Service API
-
mode - Signal<'light' | 'dark' | 'system'> The mode that was requested, whether or not the user chose it explicitly.
-
effectiveTheme - Signal<'light' | 'dark'> The resolved theme after the system preference has been taken into account.
-
setTheme(theme) - void Stores the choice, then applies the resolved theme to the document.
-
toggle() - void Cycles through light, dark and system in that order.
toggle()
-
light -
dark -
system
-
Persistence: the chosen mode is written to
localStorageunderbursit-themeand restored on the next visit. -
Application: the resolved theme is set as
bursit-themeon<html>, which is exactly what the token layer selects on. -
System mode: a
prefers-color-schemelistener keeps the interface in sync until an explicit mode is chosen.
Same markup, both themes
:root — light
surface / raised
Card title
Body copy on an elevated surface, one step above the page.
Primary subtle
.dark — dark
surface / raised
Card title
Body copy on an elevated surface, one step above the page.
Primary subtle
Identical markup and identical classes. The right panel only adds the
.dark selector, so every token inside it re-resolves — including the ones
used by primitives this page has never rendered.
import { Component, inject } from '@angular/core';
import { BursitThemeService } from 'bursit-angular';
@Component({ selector: 'app-shell', template: '...' })
export class ShellComponent {
readonly theme = inject(BursitThemeService);
switchToDark(): void {
this.theme.setTheme('dark');
}
cycle(): void {
// light -> dark -> system -> light
this.theme.toggle();
}
}