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.tsre-exports components, directives, pipes, and SCSS. - Builders –
@angular-devkit/build-ng-packagrproduces Ivy-compatible bundles (esm2020,fesm2020, typings). - Publish –
npm publishor 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 publishandnpm installbecame 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/uiproduces<20 KBmin+gz by tree-shaking unused components. - Lighthouse — Ensure colored buttons pass contrast ≥ 4.5:1; document token overrides for dark mode.
- Rendering — Angular 18
OnPushchange 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.