Skip to main content

Command Palette

Search for a command to run...

Headless UI in Angular

Published
•4 min read•View as Markdown
S

I am a full-stack developer with 9+ years of experience, passionate about the JavaScript ecosystem. I have a bachelor's degree in computer science. I am most skilled and passionate about Angular and React. I am able to provide meaningful contributions to the design, installation, testing, and maintenance of any type of software system. I like to challenge myself in new roles. I have built and successfully delivered applications in multiple domains. In my free time, I like to write blogs related to software development. I have the pleasure of working on exciting projects across industries. The applications that I developed were scalable, deployable, and maintainable. I have a vision of providing cutting-edge web solutions and services to enterprises. Developed zero-to-one products.

Building Reusable, Scalable, Framework-Agnostic Components

As applications grow, developers often face a major challenge:

How do we build reusable components without locking ourselves into a specific UI design or CSS framework?

Enter Headless UI — an architectural pattern that separates logic from presentation, enabling teams to reuse powerful components across projects, regardless of the UI library (Material, Tailwind, Bootstrap, custom styles).

Headless UI is becoming an essential approach for enterprise Angular development, design systems, and component libraries.

This article breaks down what headless UI means, why it's important, and how to implement it cleanly in Angular.


🎯 What Is Headless UI?

Headless UI components = Logic-only components.

They provide:

  • 🧠 Behavior

  • ⚙️ State management

  • 🔄 Events

  • 📦 Accessibility logic

  • 🧪 Testable business/UI logic

…but no HTML or CSS.

The UI becomes the responsibility of the consuming app.

Example Thinking:

  • A Dropdown with no pre-defined template

  • A Modal with no default styling

  • A Date Picker with no UI framework dependency

Headless UI allows teams to apply any styling system: Tailwind, Angular Material, Bootstrap, custom CSS, or even server-generated styles.


🏗️ Why Use Headless UI in Angular?

▶️ 1. Reusability Across Projects

Write the logic once → use it in multiple apps with entirely different UI styles.

▶️ 2. Framework-Agnostic Design Systems

Great for enterprise-level component libraries.

▶️ 3. Total Control Over UI

Developers can change styles without touching business logic.

▶️ 4. Easier Testing

Logic becomes isolated and testable without DOM concerns.

▶️ 5. Long-Term Maintainability

Your logic no longer depends on Material/Bootstrap/Tailwind versions.


🧠 Headless UI Architecture in Angular

A clean architecture uses 3 layers:

Headless Component (logic)
      |
      v
UI Wrapper Component (optional)
      |
      v
Application UI (HTML/CSS)

Layer Responsibilities

LayerResponsibility
1. Headless Component/ServiceState, events, calculations, accessibility logic
2. UI WrapperOptional: connects logic to template APIs
3. Application TemplateStyling, HTML structure, animations

🧪 Example: Headless Dropdown Component

Let’s implement a headless dropdown in Angular.


1️⃣ Step: Create the Logic (Headless Service)

@Injectable()
export class DropdownController {
  isOpen = signal(false);

  toggle() {
    this.isOpen.update(v => !v);
  }

  open() {
    this.isOpen.set(true);
  }

  close() {
    this.isOpen.set(false);
  }
}

This service contains no HTML or CSS — only logic.


2️⃣ Step: Provide Headless Logic to a Component

@Component({
  selector: 'headless-dropdown',
  template: `<ng-content></ng-content>`,
  providers: [DropdownController]
})
export class HeadlessDropdownComponent {
  constructor(public dropdown: DropdownController) {}
}

Why use ng-content?

Because headless components don't render UI — the consumer controls the DOM.


3️⃣ Step: Consume the Headless Component With Custom UI

<headless-dropdown #dd="dropdown">
  <button (click)="dd.toggle()">
    Toggle Menu
  </button>

  <div *ngIf="dd.isOpen()"
       class="custom-dropdown-panel">
    <ng-content select="[menu]"></ng-content>
  </div>
</headless-dropdown>

The UI is now 100% customizable, and the logic is centralized and reusable.


✨ Example: Headless Modal


Headless Logic

@Injectable()
export class ModalController {
  isOpen = signal(false);

  open() { this.isOpen.set(true); }
  close() { this.isOpen.set(false); }
}

Headless Component

@Component({
  selector: 'headless-modal',
  template: `<ng-content></ng-content>`,
  providers: [ModalController]
})
export class HeadlessModalComponent {
  constructor(public modal: ModalController) {}
}

Consumer UI

<headless-modal #m="modal">
  <button (click)="m.open()">Open Modal</button>

  <div class="backdrop" *ngIf="m.isOpen()">
    <div class="modal-box">
      <ng-content select="[modal-body]"></ng-content>
      <button (click)="m.close()">Close</button>
    </div>
  </div>
</headless-modal>

The modal’s visual appearance can change entirely without updating logic.


🧬 Headless UI With Angular Directives (Alternative Pattern)

Directives can encapsulate behavior more cleanly.

Example: Headless tooltip

@Directive({
  selector: '[tooltip]',
  exportAs: 'tooltip'
})
export class TooltipDirective {
  isVisible = signal(false);

  show() { this.isVisible.set(true); }
  hide() { this.isVisible.set(false); }
}

Consume:

<button tooltip #t="tooltip"
        (mouseenter)="t.show()"
        (mouseleave)="t.hide()">
  Hover me
</button>

<div *ngIf="t.isVisible()" class="tooltip-box">
  Tooltip content here
</div>

🏢 Enterprise Architectural Pattern:

Headless + Renderless Components + Signals

Enterprises often combine:

  • Renderless components

  • Signals for reactive state

  • Content projection

  • Directives for behavior

  • UI components for rendering

This leads to clean, reusable patterns similar to Radix UI, Headless UI (React), and Angular CDK.


🎁 Bonus: When Should You Use Headless UI?

ScenarioUse Headless UI?
Building a Design System✔ YES
Multiple Angular apps share components✔ YES
You want Material/Tailwind independence✔ YES
Components require complex behavioral logic✔ YES
Simple components with fixed UI❌ Not necessary

🔥 Final Thoughts

Headless UI is not just a trend — it’s a foundational architectural approach for large-scale, long-lived Angular applications.

It provides:

  • Total styling freedom

  • Reusability across teams

  • Framework independence

  • Cleaner separation of concerns

  • Increased testability

With Angular Signals, Dependency Injection, and content projection, Angular is one of the best frameworks for building headless UI architectures.