@dmaster/ui
v0.10.0
Published
Modern, premium Angular UI components. Standalone, signals-based, themeable via CSS variables.
Maintainers
Readme
@dmaster/ui
Premium Angular UI components. Standalone, signals-based, zoneless-ready, and fully themeable via CSS custom properties — with a clean, flat, pill-radius design language.
45 components · 3 languages (EN/ES/FR) in the docs · 0 third-party UI dependencies · 790+ tests
→ Browse every component live at dmasterui.com/components — each tile is the real, interactive component, not a screenshot.
▶ Try it now on StackBlitz — a minimal zoneless Angular 20 app already wired up with the library; no install needed. (Source: examples/starter.)
Why @dmaster/ui?
- Modern Angular API —
input()/output()/model()signals, standalone components, no NgModules, no decorators - Zoneless-ready — designed for
provideZonelessChangeDetection()from day one - Flat, pill-radius design language — flat colors, pill radii (
border-radius: 9999pxby default), elastic press scale, color × variant token system - Deeply themeable — light / dark / auto via
data-dm-theme; every visual decision is a--dm-*CSS custom property you can override - Accessible by default — ARIA attributes on host elements,
:focus-visiblefocus rings, touch targets ≥ 44px,prefers-reduced-motionsupport - CDK-powered overlays — tooltip, popover, menu, dialog, drawer, toast and command palette built on
@angular/cdk, no third-party overlay dependencies - Three density levels —
compact,comfortable,spaciousviadata-dm-density - Pay only for what you import — fully tree-shakeable: a button costs ~3.8 kB gzip, a card ~1.3 kB, the entire 45-component library ~92 kB (see Bundle size)
Installation
ng add @dmaster/uing add installs @angular/cdk, registers the CDK overlay styles and wires the global stylesheet for you. Prefer manual setup?
npm install @dmaster/ui @angular/cdkPeer dependencies:
@angular/core,@angular/common,@angular/forms,@angular/cdk(^20) andrxjs
Quick start
1. Add the global styles in styles.scss:
@use '@dmaster/ui/styles/index';No Sass in your build? Use the precompiled CSS instead — add it to the styles array in angular.json:
"styles": ["node_modules/@dmaster/ui/styles/dmaster-ui.css", "src/styles.css"]2. Add CDK overlay styles in angular.json (required for tooltip, popover, menu, dialog, drawer, toast, command):
"styles": ["node_modules/@angular/cdk/overlay-prebuilt.css", "src/styles.scss"]3. Register the provider (optional — sets default theme and density):
import { provideDmasterUI } from '@dmaster/ui';
bootstrapApplication(AppComponent, {
providers: [provideDmasterUI({ theme: 'auto', density: 'comfortable' })],
});4. Import and use any component:
import { DmButtonComponent, DmBadgeComponent } from '@dmaster/ui';
@Component({
imports: [DmButtonComponent, DmBadgeComponent],
template: `
<dm-button color="primary" variant="solid">Save</dm-button>
<dm-badge color="success" variant="flat">Active</dm-badge>
`,
})
export class MyComponent {}Engineering & quality gates
Every pull request clears the same automated bar before it can merge — these run on CI, not on trust:
- Accessibility is a gate, not a claim — axe-core (WCAG 2.1 A + AA) scans every prerendered docs page in both light and dark themes, plus every opened overlay. Zero violations required. (details)
- Visual regression — every route is screenshotted in light + dark and pixel-diffed against committed baselines, inside a pinned Playwright container so rendering is deterministic across machines and CI.
- Real-consumer integration — the library is packed with
npm packand installed into a separate, isolated Angular app (examples/starter) that builds against the published tarball — catching exports-map, peer-dependency and type-resolution breakage the monorepo (whose paths point at source) never sees. - SSR safety — the docs site is fully statically prerendered on every build, so any component that reaches for a browser-only global fails CI immediately.
- Package hygiene — publint validates the published layout on every PR; releases go to npm with signed provenance (Sigstore attestation).
- Tree-shake audit — per-component gzip cost is measured through the exact linker + minify pipeline the Angular CLI applies to published libraries, not a naive bundle. Reproducible with
npm run size. (bundle table)
The four CI jobs — build/test/lint/publint, integration, a11y and visual — run in parallel and must all be green to merge.
Components
45 components across 8 categories. Every row links to its live docs page (playground, API table, a11y notes).
Primitives
| Component | Selector | Highlights |
| ------------------------------------------------------ | ------------- | -------------------------------------------------------------------------- |
| Skeleton | dm-skeleton | Shimmer placeholder, wave animation |
| Spinner | dm-spinner | Animated indicator, 3 sizes |
| Badge / Chip | dm-badge | 7 variants · 6 colors · dot, shadow, bordered |
| Avatar | dm-avatar | Image + fallback initials, 5 sizes |
| Keyboard Key | dm-kbd | Named keys resolve to their symbol (⌘, ⇧…), content projection |
| Icon | dm-icon | Font ligature, registered SVG, or projected <svg> — 3 modes, 1 primitive |
Layout
| Component | Selector | Highlights |
| ------------------------------------------------------- | -------------- | --------------------------------------------- |
| Card | dm-card | Container queries, shadow variants |
| Accordion | dm-accordion | Animated, single/multiple open, keyboard nav |
| Divider | dm-divider | Horizontal/vertical, optional projected label |
Feedback
| Component | Selector | Highlights |
| ----------------------------------------------------- | ------------- | -------------------------------------------------------- |
| Progress | dm-progress | Determinate/indeterminate, striped, value label |
| Alert | dm-alert | Color × variant, semantic icon, dismissible, action slot |
Buttons
| Component | Selector | Highlights |
| ------------------------------------------------------------- | ----------------- | --------------------------------------------------------------------------------- |
| Button | dm-button | 7 variants · 6 colors · idle/loading/success/error states · live region |
| Button Group | dm-button-group | Attached bar with 1px seams, group-level color/variant/size cascade, split-button |
| Copy Button | dm-copy-button | Copies text with a copy→check flip · dmCopyToClipboard directive · SSR-safe |
Forms
| Component | Selector | Highlights |
| ------------------------------------------------------------- | ------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| Switch | dm-switch | ControlValueAccessor, 3 sizes |
| Checkbox | dm-checkbox | ControlValueAccessor, indeterminate state |
| Radio Group | dm-radio-group + dm-radio | ControlValueAccessor, horizontal/vertical |
| Select | dm-select | Single or multiple (chips), inline filter, groups, select-all, CVA — or server-driven: loadFn pages via rxResource, infinite scroll / load-more, debounced server search |
| Autocomplete | dm-autocomplete | Free-text input with filtered suggestions, optionSelected event, keyboard nav, CVA |
| Search Field | dm-search-field | Leading icon, clear button, Escape clears, Enter submits |
| Date Picker | dm-date-picker | Day→month→year calendar, Intl-only (no date library), reactive locale (DM_DATE_LOCALE) |
| Color Picker | dm-color-picker | Saturation/hue/alpha area, editable hex, swatches |
| Slider | dm-slider | Pointer drag + full keyboard map, value bubble, marks |
| Rating | dm-rating | Star rating, half stars, readonly fractional average, custom glyph, role="slider", CVA |
| Number Input | dm-number-input | Spinbutton with ± controls, keyboard map, clamp/precision, Intl formatting, CVA |
| OTP Input | dm-otp | One-time-code / PIN cells, 4 surface variants, group separators, paste distribution, CVA |
| Toggle Group | dm-toggle-group + dm-toggle | Segmented control, single/multiple, radiogroup or aria-pressed, roving keyboard, CVA |
| File Upload | dm-file-upload | Drag-and-drop dropzone, thumbnails, per-file progress, accept/size/count validation |
| Form Field | dm-form-field + dmInput | Label, hint, error, wraps any native input/textarea/select |
| Error | dm-error | Validation line, role="alert", no built-in icon (project your own) |
Navigation
| Component | Selector | Highlights |
| ----------------------------------------------------------- | --------------------------------------- | ------------------------------------------------------ |
| Breadcrumbs | dm-breadcrumbs + dm-breadcrumb-item | Router-agnostic, collapsible |
| Tabs | dm-tabs + dm-tab + dm-tab-panel | 5 variants, sliding indicator, full-width, scroll-fade |
| Pagination | dm-pagination | Windowed pager, two-way page binding |
| Stepper | dm-stepper + dm-step | Horizontal/vertical wizard, linear mode, step states |
Data Display
| Component | Selector | Highlights |
| ----------------------------------------------------------- | ---------------------------------- | ---------------------------------------------------------------- |
| Table | dm-table | Search → sort → paginate pipeline, row selection, virtual scroll |
| Tree | dm-tree | WAI-ARIA tree view, roving focus, single/multiple selection |
| Timeline | dm-timeline + dm-timeline-item | Vertical/horizontal events, states, custom markers, alternate |
| Empty state | dm-empty-state | Icon + title + description + actions, custom icon slot, 3 sizes |
Overlays (require CDK)
| Component | Selector / Service | Highlights |
| --------------------------------------------------- | --------------------- | ------------------------------------------------- |
| Tooltip | dmTooltip directive | 12 placements, open/close delay |
| Popover | dm-popover | Rich floating panel, arrow follows the real flip |
| Menu | dm-menu | WAI-ARIA menu, FocusKeyManager, typeahead |
| Dialog | DmDialogService | Typed data, typed return, backdrop click to close |
| Drawer | DmDrawerService | Slide-in panel over cdk/dialog |
| Toast | DmToastService | Queued notifications, signal-driven state |
| Command | dm-command | ⌘K command palette, fuzzy filter |
Theming
Everything is a CSS custom property — no Sass required to consume, no Tailwind
lock-in, no runtime engine. Theme state lives on <html> attributes
(data-dm-theme, data-dm-density), so it is SSR-safe and flips without a
flash. Start from a prebuilt theme, then customize across four levels — from a
one-line accent swap to a full custom theme.
Prebuilt themes
Nine curated palettes ship as tiny CSS files. Pick one at install — no code:
ng add @dmaster/ui # prompts for a theme
ng add @dmaster/ui --theme=ocean # or pass it directly…or add it to angular.json, right after the base styles:
"styles": [
"node_modules/@angular/cdk/overlay-prebuilt.css",
"node_modules/@dmaster/ui/styles/dmaster-ui.css",
"node_modules/@dmaster/ui/themes/ocean.css" // ← recolors the whole app
]Available: ocean · cobalt · iris · grape · rose · ember · sunset ·
forest · slate (plus the built-in blue, which needs no file). Each recolors
the brand in both light and dark — the light/dark toggle keeps working — and
every label color is gated at WCAG AA.
Custom theme — ng generate
Want your own? Scaffold one from a seed color — it writes the CSS (with every
knob commented in) and wires it into angular.json:
ng generate @dmaster/ui:theme brand --primary=#e11d48
# switchable named theme instead of a global recolor:
ng generate @dmaster/ui:theme midnight --named --scheme=darkThe default is a global recolor (:root + [data-dm-theme='dark'], so
light/dark keeps working). --named emits a [data-dm-theme='<name>'] block and
prints the provideDmasterUI line to register it. Or
just override a token by hand:
1. Override a token — the whole family cascades
Every visual decision is a --dm-* token, and each semantic color's family is
derived from its base: the soft fill (-subtle) via color-mix(), and the
hover shade (-hover) and tinted-text shade (-text) via OKLCH relative color
syntax at a fixed, calibrated lightness. Changing one base color re-themes
everything that uses it — fills, hovers, tint text, rings:
:root {
--dm-primary: #7c3aed; /* hover, -subtle, -text and rings all re-derive */
--dm-radius-full: 0.5rem; /* rectangular controls instead of pills */
--dm-duration-base: 100ms; /* faster animations everywhere */
}Because the derived shades sit at a fixed OKLCH lightness (and WCAG contrast is dominated by lightness), the derived text keeps its ≥4.5:1 ratio over its tint for arbitrary brand colors — a mid-tone orange, a teal, a crimson all come out AA out of the box.
Only
-fg(the label over solid fills, usually white) stays explicit: if your brand color is very light, also set--dm-<color>-fgto a dark value — or register the palette as a named theme (below).
2. Light / dark / auto
provideDmasterUI({ theme: 'auto' }); // follows prefers-color-scheme, tracks OS liveinject(ThemeService).setTheme('dark'); // or 'light' | 'auto'
inject(ThemeService).toggle(); // flip the active scheme3. Custom named themes
Register any number of named themes and switch between them at runtime. The name
is stamped on <html data-dm-theme="…">; you author the matching CSS block:
provideDmasterUI({
themes: {
midnight: { scheme: 'dark', label: 'Midnight' },
sand: { scheme: 'light', label: 'Sand' },
},
});[data-dm-theme='midnight'] {
color-scheme: dark;
--dm-bg: #0a0a12;
--dm-primary: #8b5cf6;
/* …only the tokens you want to change; the rest inherit the dark defaults */
}const themes = inject(ThemeService);
themes.setTheme('midnight'); // apply
themes.themes(); // [{ name, scheme, label }] — build a picker
themes.scheme(); // 'dark' — the active theme's base scheme4. Restyle a single component or a subtree
Because tokens are plain CSS custom properties, scoping an override to an element or a subtree re-themes only what's inside it — no global change, no component API needed:
/* every switch in the app turns green — components read the semantic tokens */
dm-switch {
--dm-primary: var(--dm-success);
}
/* just this section and everything under it */
.pricing {
--dm-primary: #0ea5e9;
--dm-radius-lg: 0;
}On top of that, every component exposes its own --dm-<component>-* design
tokens (~300 across the library) for the decisions the semantic set doesn't
cover — surface, radius, heights, part-specific knobs:
/* squarer, taller buttons app-wide — without touching the global radius scale */
:root {
--dm-button-radius: 0.5rem;
--dm-button-height: 3rem;
}
/* or one part of one component */
.data-panel {
--dm-table-header-bg: var(--dm-bg-muted);
--dm-switch-track-bg-checked: var(--dm-success);
}Each component folder's README.md documents its tokens in a Design tokens
table.
Overlays (tooltip, menu, dialog, drawer, toast, select, command) render at the document root via the Angular CDK, so a global override or a named theme (both applied on
<html>) reaches them, but a subtree override does not. For a scoped theme inside a dialog or drawer, pass your theme class viapanelClass:this.dialog.open(CheckoutComponent, { panelClass: 'brand-theme' });
Density
Three density scales adjust control heights and spacing globally:
provideDmasterUI({ density: 'compact' }); // 'compact' | 'comfortable' | 'spacious'SSR / prerendering
The library is server-safe: no component or service touches window, localStorage or browser-only APIs at construction time — matchMedia is feature-detected through Angular's DOCUMENT, and overlays only open on user interaction. The docs site itself is fully statically prerendered with @angular/ssr as a permanent smoke test.
Accessibility
Accessibility isn't a checkbox here — it's a gate. Every prerendered docs page (which exercises every component and variant) is scanned with axe-core for WCAG 2.1 Level A + AA compliance, in both light and dark themes, plus every overlay opened — and the suite must be green on every pull request.
Concretely, components ship with: semantic roles and ARIA wired on the host, full keyboard support (roving focus, typeahead, focus traps in overlays), visible :focus-visible rings, ≥44px touch targets, prefers-reduced-motion honored, and a color system where every text/fill pair — including the vivid flat / light / faded variants — clears the 4.5:1 contrast ratio in both themes. Icon-only dm-buttons take an ariaLabel; form controls forward validity through aria-describedby to dm-error.
Run it yourself against the prerendered build:
npm run build:app && npm run test:a11yBundle size
Fully tree-shakeable — your app only pays for the components it imports. Measured with the same pipeline the Angular CLI applies to published libraries (linker + minify + gzip, @angular/* and rxjs external):
| Import | Gzip |
| ------------------------- | -------- |
| DmCardComponent | ~1.3 kB |
| DmBadgeComponent | ~1.8 kB |
| DmButtonComponent | ~3.8 kB |
| DmSelectComponent | ~10.1 kB |
| DmTableComponent | ~11.8 kB |
| Entire library (45) | ~92 kB |
| Global CSS (tokens+reset) | ~4.7 kB |
Reproduce the full per-component table from the repo with npm run size.
Who is this for?
@dmaster/ui is a good fit for:
- SaaS dashboards — tables, forms, overlays, and toasts out of the box
- Admin panels — dense layouts with consistent design tokens and three density modes
- Component-library starters — use as a base or reference architecture for signals-based Angular components
- Teams migrating to zoneless Angular — built for
provideZonelessChangeDetection()from day one
Compared to other Angular libraries
There's no shortage of component libraries in the Angular ecosystem. Here's how @dmaster/ui differs from the ones you're most likely evaluating alongside it — read this as "different tradeoffs," not "objectively better":
| | @dmaster/ui | Angular Material | PrimeNG | Taiga UI | Spartan · ng-primitives |
| ------------------- | ---------------------------------------------------------------- | -------------------------------------------------- | -------------------------------------------- | ---------------------------------------- | ------------------------------------------------------------------------ |
| API style | input()/output()/model() signals, standalone, no NgModules | Mix of legacy + newer APIs, NgModule-based | NgModule + standalone hybrid | Signals, standalone | Signals, standalone |
| What you get | Pre-styled components, own flat/pill design language | Pre-styled components (Material Design) | Pre-styled components, several theme presets | Pre-styled components, own design system | Unstyled primitives — copy-paste styled blocks you own, shadcn-style |
| Theming | CSS custom properties only, no runtime engine | Sass theming API + Material 3 design tokens | Runtime theme switcher + design tokens | CSS custom properties | Tailwind-based, per-component |
| Component count | 45 | Comprehensive Material Design set (CDK + Material) | Very large, kitchen-sink surface area | 130+ (their own count) | 55+ primitives (their own count) |
| Zoneless | Built for it from day one | Supported | Supported | Supported | Supported |
| Install model | ng add, then import & use | ng add, then import & use | Install & import | Install & import | CLI copies component source into your repo |
If you want a batteries-included, opinionated kit you install and start using immediately — that's @dmaster/ui, Angular Material, PrimeNG or Taiga UI, and the choice between them comes down to design language and API generation. If you want unstyled, accessible primitives you fully own the markup and CSS for, Spartan/ng-primitives is the better starting point.
Contributing
Issues and PRs are welcome on GitHub.
License
MIT © Diego Maestro
