Hook — 05 : 55 a.m., Cartagena ↔ São Paulo sprint demo
Waves slapped the old city walls while I screenshared a prototype “Button → Card” design system with Lúcio, a junior dev dialing in from Brazil. We’d shipped four Angular apps that month—each with its own slightly different primary-button component. Marketing’s red turned to maroon in the checkout flow, and QA complained of mis-aligned tooltips on low-dpi tablets. “Enough copy-paste,” I said, sipping tinto. We opened the Angular CLI, generated a workspace library, published it to our private Verdaccio registry, and within an hour every product shared one source of visual truth. – That cross-continent refactor is today’s roadmap: you’ll learn how to architect, package, version, and consume a reusable UI library in Angular 18, without losing design fidelity—or your teammates’ sanity across time zones.
Why Roll Your Own Library?
Pain Point | What Hurts | Angular Library Fix |
---|---|---|
Diverging button styles across apps | Brand inconsistency, higher QA time | Single source of CSS + TS truth |
Copy-pasted components | Bloated bundles, tangled bug fixes | Ship once, consume everywhere |
Multiple teams, multiple repos | Merge conflicts & code drift | Workspace libraries live beside apps, versioned together |
Design token changes | Manual edits in every project | Library exposes SCSS or CSS Vars; update package, bump version |
A solid library means faster sprints, lower bundle sizes, and a UI that feels coherent from Mexico City fintech dashboards to Costa Rican surf-school booking sites.
Anatomy of an Angular Library
- Workspace – monorepo root (
angular.json
) managing apps and libs. - Library – generated under
projects/
with its ownng-package.json
. - Entry Point –
public-api.ts
re-exports components, directives, pipes, and SCSS. - Builders –
@angular-devkit/build-ng-packagr
produces Ivy-compatible bundles (esm2020
,fesm2020
, typings). - Publish –
npm publish
or private Verdaccio/Artifactory.
Step-by-Step Walkthrough
1 — Create a Workspace and Library
bashCopyEdit# ① scaffold workspace without an initial app
ng new latin-ui --create-application=false --style=scss
cd latin-ui
# ② generate a library
ng g library ui --prefix=lu --style=scss
Folder tree:
cssCopyEditprojects/ui/
src/
lib/
button/
button.component.ts
button.component.html
button.component.scss
public-api.ts
ng-package.json
2 — Build the First Component
tsCopyEdit// button.component.ts
import { Component, Input } from '@angular/core';
@Component({
selector: 'lu-button',
template: `<button
[class]="variant"
[disabled]="disabled"
type="button">
<ng-content></ng-content>
</button>`,
styleUrls: ['./button.component.scss'],
})
export class ButtonComponent {
@Input() variant: 'primary' | 'secondary' | 'danger' = 'primary';
@Input() disabled = false;
}
scssCopyEdit/* button.component.scss */
$primary: #005dff;
$secondary: #64748b;
$danger: #ef4444;
button {
padding: .5rem 1rem;
border-radius: .5rem;
font-weight: 600;
&.primary { background: $primary; color: #fff; }
&.secondary { background: $secondary; color: #fff; }
&.danger { background: $danger; color: #fff; }
&:disabled { opacity: .5; cursor: not-allowed; }
}
Export via public-api.ts
:
tsCopyEditexport * from './src/lib/button/button.component';
3 — Build & Link Locally
bashCopyEditng build ui
# link for local dev
npm link dist/ui
In any local Angular app:
bashCopyEditnpm link @latin/ui
Import module (Angular 18 standalone):
tsCopyEditbootstrapApplication(AppComponent, {
providers: [importProvidersFrom(UiModule)]
});
4 — Add a Theming Hook with SCSS or CSS Variables
Expose design tokens:
scssCopyEdit// projects/ui/src/styles/_tokens.scss
:root {
--lu-primary: #005dff;
--lu-secondary: #64748b;
--lu-danger: #ef4444;
}
In component styles use var(--lu-primary)
; apps override via global stylesheet—no rebuild of library needed.
Publishing & Versioning Strategy
Stage | Command | Notes |
---|---|---|
Build for prod | ng build ui --configuration=production | Produces Ivy & View Engine bundles |
Bump version | npm version minor | Follow semver; changelog via commitlint |
Publish | npm publish dist/ui --access public or --registry http://your-verdaccio | Scope under @latin |
Consume | npm i @latin/ui@^1.3.0 | Apps pick up compatible majors |
Tip — Use Nx or Bazel for incremental builds in huge mono-repos; Angular 18 builder caching already speeds cold builds ~60 %.
Common Pitfalls & Fixes
Pitfall | Symptom | Remedy |
---|---|---|
Forgot to add component to exports: | App template error: “’lu-button’ is not a known element” | Add to @NgModule({ exports: […] }) or export in public-api.ts |
Mixing View Engine & Ivy | Build fails with NG6002 | Always build libraries with Angular 18+; remove enableIvy:false |
Shadow DOM leak | Styles override host app | Use ::ng-deep only for purposeful overrides; prefer encapsulated SCSS |
Breaking changes in minor | Apps crash after npm update | Follow semver strictly; bump major for API breaks |
Remote-Work Insight ☕
While freelancing from Panama City, we had satellite internet with 500 ms latency. Publishing to public npm took forever. We mirrored a Verdaccio registry on a local Raspberry Pi in the coworking.
npm publish
andnpm install
became LAN-fast; weekly design tweaks rolled out to three micro-frontends before the afternoon storm cut power.
Performance & Accessibility Checkpoints
- Bundle Size —
npm pack dist/ui
produces<20 KB
min+gz by tree-shaking unused components. - Lighthouse — Ensure colored buttons pass contrast ≥ 4.5:1; document token overrides for dark mode.
- Rendering — Angular 18
OnPush
change detection by default in library components; avoid two-way binding inside. - Keyboard & ARIA — Expose
[attr.aria-label]
pass-throughs; publish Storybook docs with keyboard scenarios.
Call-Out Table — Key CLI Commands
CLI | Purpose |
---|---|
ng g library ui --style=scss --prefix=lu | Generate library |
ng build ui | Bundle Ivy-ready package |
ng test ui | Run Jest/Karma tests for lib |
ng add @storybook/angular | Docs + visually test components |
npm publish dist/ui | Distribute package |
SVG Diagram Idea
Lane 1 — Developer: ng g library ui
➜ code components.
Lane 2 — Builder: ng build ui
➜ ng-packagr
bundles (fesm2020
, d.ts
).
Lane 3 — Registry: publish package.
Lane 4 — Consumer App: npm install @latin/ui
➜ imports & treeshakes.
Wrap-Up
A reusable UI library is a passport between projects and teams: design changes propagate in minutes, not sprints; bug fixes land once, not everywhere. Angular 18’s library tooling, Ivy build graph, and standalone components make the process almost vacation-easy—perfect for devs juggling hot-fixes from Dominican rooftops to Colombian coffee farms. Start small (buttons), automate versioning, enforce tokens, and watch your front-end velocity soar.
Questions or stories from your own library adventures? Drop a comment—I’ll answer between layovers and late-night arepa sessions.