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.

  1. 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>

  2. 02

    Tokens own the visuals

    Every color, space, radius and shadow resolves to a var() from bursit-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)

  3. 03

    Standalone by default

    There is no NgModule anywhere 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-xs Directive-first
  • --font-size-sm Directive-first
  • --font-size-base Directive-first
  • --font-size-lg Directive-first
  • --font-size-xl Directive-first
  • --font-size-2xl Directive-first
  • --font-size-3xl Directive-first
  • --font-size-4xl Directive-first
  • --font-size-5xl Directive-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.

theme.css customization

              /* 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

    Directive

    Applies button styling to native elements. The color input selects primary, secondary, outline, link or danger.

    • [bursitButton]
  • InputDirective

    Directive

    Reactive states for input and textarea — focus, hover, invalid, disabled and an optional floating label, driven by NgControl.

    • [bursitInput]
  • LabelDirective

    Directive

    Label that binds its for attribute to the field id published by FormField.

    • [bursitLabel]
  • TooltipDirective

    Directive

    Overlay tooltip bound to focus and hover, with configurable position, show and hide delays, and an optional arrow.

    • [bursitTooltip]
  • Error

    Directive

    Error slot for a field. Renders with role="alert" and takes part in the control’s described-by chain.

    • [bursitError]
  • Modal slots

    Directive

    Named content slots projected into the modal chrome.

    • [bursitModalHeader]
    • [bursitModalBody]
    • [bursitModalFooter]
  • FormField

    Component

    Groups label, control, error and helper text. Generates the field id and exposes group semantics.

    • <bursit-form-field>
  • Select

    Component

    Select built on CDK Overlay with full keyboard support and a ControlValueAccessor.

    • <bursit-select>
  • Option

    Component

    A single option inside bursit-select. Its label is read from the projected text.

    • <bursit-option>
  • Checkbox

    Component

    Checked, indeterminate and disabled states, wired into Angular forms.

    • <bursit-checkbox>
  • Message

    Component

    Helper text slot, linked to the control it describes.

    • <bursit-message>
  • Avatar

    Component

    Image avatar that falls back to initials computed from userName, in six sizes from xs to 2xl.

    • <bursit-avatar>
  • Icon

    Component

    Lucide-backed icon with name, size, color and strokeWidth inputs. Inherits currentColor by default.

    • <bursit-icon>
  • Modal

    Component

    Dialog chrome with a focus trap and three named slots. Opened through ModalService.

    • <bursit-modal>
  • Toast

    Component

    Toast overlay and a single toast item, with success, info, warning and error variants.

    • <bursit-toast-container>
    • <bursit-toast-item>
  • BursitThemeService

    Service

    Light, dark and system themes. Applies the bursit-theme attribute to <html> and persists the choice.

    • inject(BursitThemeService)
  • ToastService

    Service

    Imperative toasts. show(options) returns a ToastRef with dismiss() and an afterClosed() stream.

    • inject(ToastService)
  • ModalService

    Service

    Opens a component in a CDK overlay and returns a ModalRef with close(), dismiss() and afterClosed().

    • inject(ModalService)

See every primitive live in the Storybook

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.

  1. 01

    Install and wire the tokens

    The schematic installs the package and appends the bursit-ui-tokens stylesheet to the styles array of every project in angular.json.

    terminal bash
     ng add bursit-angular
  2. 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.

    account.component.ts typescript
    
                      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 });
                    
                      }
                    
  3. 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.

    Open the Storybook

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()

  1. light
  2. dark
  3. system
  • Persistence: the chosen mode is written to localStorage under bursit-theme and restored on the next visit.
  • Application: the resolved theme is set as bursit-theme on <html>, which is exactly what the token layer selects on.
  • System mode: a prefers-color-scheme listener 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.

shell.component.ts typescript

              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();
            
              }
            
              }