Get License
angulartailwindcsscomponent-librarydeveloper-experience

Your UI library shouldn't live in node_modules

Angular developers have spent a decade fighting ::ng-deep and waiting on maintainers to accept a PR. Base UI takes the other path: the component source is copied into your project, and it's yours.

7 min read Base UI team

Every Angular developer has had this afternoon.

A designer asks for a slightly different card — the header needs a divider, the padding is off by a step, the footer should stack on mobile. You open the component's docs. There's no input for it. You check the source in node_modules, find the class you need to override, and write this:

::ng-deep .p-card .p-card-header {
  border-bottom: 1px solid var(--surface-border);
}

It works. It also breaks in a minor version bump eight months later, in a different part of the app, in a way nobody connects back to this line. You've traded a five-minute change for a permanent liability.

The problem isn't the library. The problem is the boundary. A traditional component library puts the code you most need to change on the wrong side of a wall, then hands you a keyhole — a handful of inputs, a theming API, a CSS custom property or two — and asks you to express every design decision through it.

The other model

Base UI takes the approach popularized by shadcn/ui in the React world and builds it properly for Angular: the component source is copied into your project.

npx base-ui-cli add card dialog data-table

That command doesn't add a dependency. It writes real .ts, .html, and .spec.ts files into your components/ folder. Dependencies between components are resolved recursively, and any npm packages a component needs are installed with your package manager automatically.

From that moment the code is yours. Need a divider in the card header? Open the file and add one. Need to rip out half the template? Do it. There is no wall, no override cascade, no ::ng-deep, no waiting on a maintainer to accept a PR for a change only your app needs.

This is not a novel idea. What's been missing is an Angular implementation that takes the model seriously — including the part everyone skips.

The obvious objection

If the code is copied, you don't get updates. That's the trade everyone assumes they're making, and it's the reason most teams stay on node_modules libraries despite the friction.

Base UI ships the answer as a first-class command:

npx base-ui-cli diff

diff shows you what changed upstream for every component you've installed. Not a changelog you have to map onto your code — the actual diff, per file.

npx base-ui-cli update

update pulls those changes in. Files you never touched update automatically. Files you customized and that also changed upstream drop into an interactive three-way merge: keep yours, take upstream, or write both side by side and reconcile at your leisure.

That's the whole trick. Ownership without abandonment. You get the thing that makes copied code good — total control — without the thing that makes it bad — a fork that rots the day you create it.

What this changes day to day

Design reviews stop ending in workarounds. When the answer to "can we change this?" is always yes, the conversation moves on instead of stalling into a debate about which override is least awful.

Your bundle contains what you use. There is no library barrel to tree-shake around, no unused module graph, no providers you're importing to get one directive.

Debugging goes through your own code. A stack trace points at a file in your repo that you can read, set a breakpoint in, and edit. Not a minified frame in a package you can't step through.

Onboarding is reading, not archaeology. A new developer opens the component and sees an ordinary Angular component. There's no framework-within-a-framework to learn first.

A concrete example

Here's a real form built from Base UI primitives. Nothing about it is unusual — that's the point:

<base-card class="shadow-xl border-blue-300">
  <base-card-header>
    <h3 class="text-xl font-bold text-slate-900 dark:text-white !mt-0 mb-4">
      Create account
    </h3>
  </base-card-header>

  <base-card-body class="p-6">
    <base-input-group class="mb-6">
      <base-label>Username</base-label>
      <input base-input type="text" placeholder="Enter username" />
      <base-error>Required field</base-error>
    </base-input-group>
  </base-card-body>

  <base-card-footer class="justify-end gap-2">
    <button base-button color="primary" size="default">Save</button>
    <button base-stroked-button color="default" size="default">Cancel</button>
  </base-card-footer>
</base-card>

Two details worth noticing.

The inputs and buttons are attribute directives (base-input, base-button), not wrapper components. You're styling a real <button> and a real <input>. Native accessibility behavior, native form participation, and Angular's ReactiveFormsModule all work because there's nothing sitting between you and the element.

And every component accepts a class input that merges intelligently with its own styles — that's shadow-xl border-blue-300 on the card and justify-end gap-2 on the footer. Conflicting Tailwind utilities are resolved rather than concatenated, so your class wins without !important and without specificity games.

Tailwind, with no configuration ceremony

Base UI uses standard Tailwind CSS 4. Standard colors, standard spacing scale. color="primary" maps to blue-*. There's no theme file to install, no design-token package, no @theme block you're required to copy before anything renders correctly.

If you want your own brand color, override Tailwind's blue scale and every component follows:

:root {
  --color-blue-500: rgb(139 92 246); /* violet */
}

That's the entire customization step. Components keep using blue-* and color="primary"; they just render violet now.

Getting started

The free tier is genuinely usable — all the primitives and all 19 form blocks, no account and no license key:

npx base-ui-cli init
npx base-ui-cli add button card dialog input-group

init writes your config, creates a stylesheet with the CDK overlay styles and keyframes, and downloads the icon sprites. npx base-ui-cli list shows everything available.

Then open one of the files it wrote. Change something. Nothing will stop you — that's the whole idea.


Browse the full catalog at base-ui.net →

Base UI is an Angular 21 + Tailwind CSS 4 component library. 187 components and blocks, 102 of them free forever. Pro is a one-time $99 license with lifetime updates.

Start with the free tier

All primitives, all 19 form blocks, and 390 icons — no account, no licence key.

Get started

Theme Customizer

Customize your UI instantly.

Primary Color

Border Radius

Background

Mode

Copy these styles to paste into your project's global CSS and Tailwind config.