Angular
NibCSS in Angular through the official builders: no plugin file, no second process — plus the directive, service, pipe and dark-mode toggle.
Angular hides its bundler, so @nibcss/angular ships builders instead of a plugin: swap the two
targets in angular.json and ng serve / ng build keep working exactly as before.
1. Create a project#
npm i -g @angular/cli # or use npx
ng new my-app --style=css
cd my-app
ng serve # http://localhost:42002. Install#
npm i @nibcss/angular @nibcss/runtime3. Point the two targets at NibCSS#
// angular.json
{
"architect": {
"build": {
"builder": "@nibcss/angular:application",
"options": { /* …exactly what you had before… */ }
},
"serve": {
"builder": "@nibcss/angular:dev-server",
"configurations": { /* …unchanged… */ }
}
}
}That is the whole setup — no second process, no @import to maintain, no plugin file to write. Every
option @angular/build:application and @angular/build:dev-server accept is accepted here
unchanged and passed straight through.
What the builders do:
- read
nibcss.config.ts, so every option in it applies exactly as it does in a Vite project - scan your templates and components, write the stylesheet, and add it to
stylesahead of your own, so your CSS always wins - publish the resolved config as
@nibcss/configand compile it into the bundle, so a dynamic[nib]binding resolves to the very class names the stylesheet holds - rebuild when a template or the config changes, without restarting the server
- with
debug: true, print each problem with its file and line, and mount the overlay onng serveonly — a production bundle never carries it
A nib block on the build target covers only where files are; everything about styling stays in
nibcss.config.ts:
"options": {
"nib": {
"out": "src/nib.css",
"config": "nibcss.config.ts",
"include": ["src/**/*.{html,ts}"],
"exclude": ["**/*.spec.ts"],
"devtools": true,
"scaffold": true
}
}Run ng serve once and nibcss.config.ts is created at the project root, with src/nib.css and
src/nib.config.mjs written beside your sources — the half of the project Angular's watcher actually
looks at.
4. Provide the runtime#
// src/app/app.config.ts
import { ApplicationConfig } from "@angular/core";
import { provideNib } from "@nibcss/angular";
export const appConfig: ApplicationConfig = {
providers: [provideNib()],
};provideNib() with no argument is correct: the builder compiles the resolved config into the bundle,
so the runtime already knows your prefix, tokens and recipes. Pass a partial to override part of it
— provideNib({ tokens: { color: { accent: "#f0f" } } }) adds to the palette rather than replacing
it.
5. A component#
// src/app/app.ts
import { Component, signal } from "@angular/core";
import { NIB_DIRECTIVES, NibService } from "@nibcss/angular";
@Component({
selector: "app-root",
imports: [...NIB_DIRECTIVES],
template: `
<main nib="mxw=720px; mi=auto; p=$space.xl $space.md; d=flex; flxd=column; g=$space.lg">
<header nib="d=flex; ali=center; jsc=space-between; g=$space.md">
<h1 nib="m=0; fts=28px; ftw=800; lts=-.02em">Dashboard</h1>
<!-- hides itself when dark mode cannot be toggled -->
<button nib="btn" nibDarkToggle>{{ nib.isDark() ? "Light" : "Dark" }}</button>
</header>
<section nib="d=flex; flxd=column; g=$space.md; md:flxd=row">
<article nib="card-hover; flg=1"><h2 nib="m=0; fts=18px">Revenue</h2></article>
<article nib="card-hover; flg=1"><h2 nib="m=0; fts=18px">Users</h2></article>
</section>
<!-- a value only the runtime can resolve -->
<input type="range" min="4" max="48" [value]="pad()"
(input)="pad.set(+$any($event.target).value)"
nib="w=100%; acc=$color.brand" />
<div [nib]="'p=' + pad() + 'px; bd=1px solid $color.border; bdrd=$radius.md'">
padding: {{ pad() }}px
</div>
<!-- the record form, like [class] -->
<div [nib]="{ 'cl=#b91c1c': invalid(), 'ftw=700': true }">state</div>
<!-- the pipe, when you need a class string -->
<span [class]="'btn' | nibClass">a class string</span>
</main>
`,
})
export class App {
protected readonly pad = signal(16);
protected readonly invalid = signal(false);
constructor(protected readonly nib: NibService) {}
}6. The API#
Export | What it is |
|---|---|
| start the runtime as the environment injector is built |
| the directive — a string, a record ( |
|
|
| a class string in a template |
| toggles on click, and hides itself when |
| all three, for a standalone component's |
| the injection tokens |
| drop the shared instance (tests) |
| the config helpers the provider uses |
Common pitfalls#
- Leaving the default
@angular/buildbuilders in place — nothing scans your templates, so the stylesheet is never written. - Forgetting
provideNib(): the CSS exists but no classes are applied. - Forgetting
imports: [...NIB_DIRECTIVES]in a standalone component —[nib]is then just an unknown attribute. - Expecting
[nib]and[class]to conflict — they do not; the directive applies throughRenderer2.