Skip to content

Latest commit

Β 

History

116 Commits

Folders and files

NameName
Last commit message
Last commit date
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 

Repository files navigation

πŸͺ¨ Quartz Headless

Unstyled, accessible Angular 21 UI primitives.

You own the styles β€” Quartz owns the behaviour.

core npm version primitives npm version CI/CD core size Angular license

🌐 Live docs & demos Β· πŸ“¦ Core Β· πŸ“¦ Primitives Β· πŸ› Report a bug


Quartz is the behaviour layer for Angular design systems β€” overlay positioning, focus traps, drag & drop, keyboard navigation and reactive state β€” with zero CSS opinions. It is Radix UI / Headless UI for Angular, combined with the shadcn/ui "copy the source into your project" distribution model.

Quartz ships as two packages: @quartz-headless/core (low-level infrastructure β€” think Angular CDK) and @quartz-headless/primitives (accessible UI patterns built on Core).

// Behaviour in, styling yours.
import { OverlayTriggerDirective } from '@quartz-headless/core';

@Component({
  imports: [OverlayTriggerDirective],
  template: `
    <button qzOverlayTrigger [overlayTemplate]="menu" placement="bottom-start">Open</button>
    <ng-template #menu><div class="your-styles">…</div></ng-template>
  `,
})
export class Demo {}

✨ Why Quartz

🎨 No visual theme No theme or visual design opinions. Structural portal/layout styles only where behaviour requires them; use data-qz-* hooks for your UI.
⚑ Zoneless Built for provideZonelessChangeDetection() β€” signals all the way down.
β™Ώ Accessible WAI-ARIA roles, focus management and full keyboard support baked in.
🌳 Tree-shakeable Standalone directives & services with no import-time side effects.
πŸ–₯️ SSR-safe Guards DOM access so it runs cleanly under Angular server rendering.
πŸ“¦ Two ways to ship Install the npm package or copy the raw source with the CLI β€” your call.

πŸš€ Install

Option A β€” npm packages

npm install @quartz-headless/core @quartz-headless/primitives
# or just the core package if you're building your own patterns on top of it:
npm install @quartz-headless/core
// app.config.ts
import { provideZonelessChangeDetection } from '@angular/core';

export const appConfig: ApplicationConfig = {
  providers: [provideZonelessChangeDetection()],
};

The previous unscoped quartz-headless package is frozen at its last published version β€” new work happens in the two packages above.

Option B β€” copy the source (shadcn-style)

The CLI copies raw TypeScript into your project, so you own the code and can modify it freely.

git clone https://github.com/Andersseen/quartz.git
cd quartz && pnpm install

pnpm quartz list                    # see everything available
pnpm quartz add overlay             # add one Core piece (+ its Core deps)
pnpm quartz add dialog splitter     # add several at once
pnpm quartz add toast --output src/app/ui

Files land flat in your project's src/lib/components/<name>/ (auto-detected) or the --output path. Core pieces (overlay, dismiss, focus, collection, directionality, viewport, drag-drop, virtual-scroll, splitter) stay pure copy-source with zero npm dependencies β€” copying one pulls in its Core siblings too (e.g. overlay also copies dismiss and directionality). Primitives (dialog, tooltip, toast, tree, listbox, menu, popover) depend on @quartz-headless/core as a real npm package instead of copied source β€” pnpm quartz add dialog copies only dialog/ and tells you to npm install @quartz-headless/core.

🧩 Primitives

Primitive Package What it gives you
overlay @quartz-headless/core Portal-based positioning for dropdowns, menus and popovers
dialog @quartz-headless/primitives Service-driven dialog & drawer with backdrop and focus trap
splitter @quartz-headless/core Resizable panels with keyboard, touch and ARIA slider semantics
toast @quartz-headless/primitives Notification system with position groups and auto-dismiss
drag-drop @quartz-headless/core Native HTML drag & drop with sortable drop zones
tooltip @quartz-headless/primitives Hover/focus tooltip with configurable placement
tree @quartz-headless/primitives Tree view with roving tabindex, WAI-ARIA keyboard nav & lazy per-level loading
listbox @quartz-headless/primitives Single/multi selection with WAI-ARIA keyboard navigation and type-ahead
menu @quartz-headless/primitives Dropdown menu with submenus, checkbox/radio items, RTL and type-ahead
popover @quartz-headless/primitives Non-modal interactive floating content with dismiss and optional initial focus
virtual-scroll @quartz-headless/core Windowed rendering for long lists
viewport @quartz-headless/core Reactive breakpoint service + ViewportMatchDirective
directionality @quartz-headless/core LTR/RTL resolution + logical inline-start/end and keyboard helpers

Every primitive is zoneless, standalone and tree-shakeable. Drag & drop follows the browser's native pointer-based HTML DnD model; keyboard drag-and-drop is intentionally deferred to a dedicated future primitive.

πŸ›  Example

import { Component, inject, ViewContainerRef, TemplateRef, viewChild } from '@angular/core';
import { DialogService } from '@quartz-headless/primitives';

@Component({
  template: `
    <button (click)="open()">Open dialog</button>
    <ng-template #tpl let-ref>
      <div class="your-modal">
        <h2>Delete item?</h2>
        <button (click)="ref.close()">Cancel</button>
      </div>
    </ng-template>
  `,
})
export class Example {
  private dialog = inject(DialogService);
  private vcr = inject(ViewContainerRef);
  private tpl = viewChild.required<TemplateRef<unknown>>('tpl');

  open() {
    this.dialog.open(this.tpl(), this.vcr, { position: 'center' });
  }
}

πŸ“‹ Requirements

Dependency Version
@angular/core ^21.0.0 (zoneless)
@angular/common ^21.0.0
Node.js >= 20

No runtime dependencies beyond @angular/*, rxjs and tslib.

πŸ’» Local development

pnpm install
pnpm start          # dev server β†’ http://localhost:5173
pnpm build:lib      # build both libraries β†’ packages/core/dist/, packages/primitives/dist/
pnpm test           # unit tests (Vitest)
pnpm e2e            # end-to-end tests (Playwright)
pnpm typecheck      # type check both libs + app
pnpm lint           # lint

The demo/docs site is an AnalogJS app deployed to Cloudflare Pages (pnpm pages:deploy). See docs/ai/ for architecture and contribution notes β€” in particular, read docs/ai/ARCHITECTURE.md before changing how the two packages resolve each other; the constraints there (why cross-package imports must go through node_modules and not a source-pointing tsconfig path) aren't obvious and are easy to accidentally break.

Adding a primitive

  1. Create packages/core/src/<name>/ (low-level infrastructure) or packages/primitives/src/<name>/ (accessible UI pattern) following the existing pattern.
  2. Export it from that package's src/public-api.ts.
  3. Register it in cli/registry.js (name, layer, files, deps for Core-internal siblings or peerDeps: ['@quartz-headless/core'] for a new Primitive).
  4. Add a demo page at src/app/pages/(docs)/<name>.page.ts, importing from @quartz-headless/core or @quartz-headless/primitives as appropriate.

🀝 Contributing

Issues and PRs are welcome. Run pnpm test, pnpm lint and pnpm typecheck before opening a PR β€” the pre-commit hook and CI enforce all three.

πŸ“„ License

MIT Β© Andersseen

About

πŸͺ¨ Unstyled, accessible Angular 21 UI primitives β€” you own the styles, Quartz owns the behaviour. Zoneless, signals-first, SSR-ready.

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Contributors

Languages