Skip to main content

Overview

Happy Habitat uses DaisyUI v5.1.7 as a component library built on top of TailwindCSS. DaisyUI provides pre-designed, semantic component classes that reduce the need for custom styling while maintaining full customization capabilities.

Installation

DaisyUI is installed as a dependency alongside TailwindCSS:
package.json
{
  "dependencies": {
    "daisyui": "^5.1.7",
    "tailwindcss": "^4.1.13"
  }
}

Configuration

Plugin Setup

DaisyUI is configured as a plugin in src/styles.css using Tailwind v4’s @plugin directive:
src/styles.css
@import "tailwindcss";
@plugin "daisyui"{
  themes: lemonade --default, dark --prefersdark, forest, winter, lemonade, autumn, lofi;
  root: ":root";
  include: ;
  exclude: ;
  logs: true;  
}

Configuration Options

  • themes: Available themes (lemonade, dark, forest, winter, autumn, lofi)
  • —default: Default theme (lemonade)
  • —prefersdark: Theme used when user prefers dark mode (dark)
  • root: CSS selector for theme application (:root)
  • logs: Enable console logs for debugging

Theme System

Available Themes

Happy Habitat includes 6 pre-configured themes:
  1. Lemonade (default) - Light, vibrant theme
  2. Dark - Dark mode theme
  3. Forest - Nature-inspired green theme
  4. Winter - Cool, minimal theme
  5. Autumn - Warm, earthy theme
  6. Lofi - Calm, low-contrast theme

Theme Switching

The application implements dynamic theme switching via the navbar:
nav-bar.component.ts
import { Component, signal, effect } from '@angular/core';

@Component({
  selector: 'hh-nav-bar',
  templateUrl: './nav-bar.component.html'
})
export class NavBarComponent {
  themeKey = 'HHTheme';
  selectedTheme = signal<string>('lemonade');
  
  themes = [
    { value: 'lemonade', label: 'Lemonade' },
    { value: 'dark', label: 'Oscuro' },
    { value: 'winter', label: 'Winter' },
    { value: 'forest', label: 'Bosque' },
    { value: 'autumn', label: 'Autumn' },
    { value: 'lofi', label: 'Lofi' }
  ];
  
  constructor() {
    effect(() => {
      const theme = this.selectedTheme();
      if (theme) {
        document.documentElement.setAttribute('data-theme', theme);
      }
    });
  }
  
  onSeleccionChange(valor: string): void {
    document.documentElement.setAttribute('data-theme', valor);
    localStorage.setItem(this.themeKey, valor);
    this.selectedTheme.set(valor);
  }
}
nav-bar.component.html
<select 
  class="select select-bordered select-sm w-24 sm:w-28 md:w-32" 
  [ngModel]="selectedTheme()"
  (ngModelChange)="onSeleccionChange($event)">
  @for (theme of themes; track theme.value) {
    <option [value]="theme.value">{{ theme.label }}</option>
  }
</select>

Theme Persistence

Theme preference is stored in localStorage:
// Save theme
localStorage.setItem('HHTheme', 'dark');

// Load theme on init
const storedTheme = localStorage.getItem('HHTheme');
if (storedTheme) {
  document.documentElement.setAttribute('data-theme', storedTheme);
}

Core Components

Buttons

DaisyUI provides semantic button variants:
<!-- Primary button -->
<button class="btn btn-primary">Primary</button>

<!-- Button sizes -->
<button class="btn btn-sm">Small</button>
<button class="btn">Normal</button>
<button class="btn btn-lg">Large</button>

<!-- Button states -->
<button class="btn btn-primary" disabled>Disabled</button>
<button class="btn btn-ghost">Ghost</button>
<button class="btn btn-circle">O</button>

<!-- Button with loading -->
<button class="btn btn-primary" [disabled]="isLoading()">
  @if (isLoading()) {
    <span class="loading loading-spinner loading-sm"></span>
    Loading...
  } @else {
    Submit
  }
</button>

Forms

Input Fields

<div class="form-control w-full mb-4">
  <label class="label">
    <span class="label-text">Username</span>
  </label>
  <input
    type="text"
    placeholder="Enter username"
    class="input input-bordered w-full"
    [class.input-error]="hasError"
    formControlName="username"
  />
</div>

Select Dropdowns

<div class="form-control w-full mb-4">
  <label class="label">
    <span class="label-text font-semibold">Select Profile</span>
  </label>
  <select class="select select-bordered w-full select-primary">
    <option value="" disabled>-- Select an option --</option>
    <option value="1">Option 1</option>
    <option value="2">Option 2</option>
  </select>
</div>

Form Validation States

<!-- Error state -->
<input class="input input-bordered input-error" />

<!-- Success state -->
<input class="input input-bordered input-success" />

<!-- Warning state -->
<input class="input input-bordered input-warning" />

Cards

Cards are used extensively for content containers:
<div class="card w-full max-w-md bg-base-100 shadow-xl">
  <div class="card-body">
    <h2 class="card-title justify-center text-2xl mb-4">Card Title</h2>
    <p>Card content goes here</p>
    <div class="card-actions justify-end">
      <button class="btn btn-primary">Action</button>
    </div>
  </div>
</div>

Alerts

Alerts provide contextual feedback messages:
<!-- Success alert -->
<div class="alert alert-success shadow-lg mb-2">
  <div class="flex items-center gap-2">
    <svg class="stroke-current shrink-0 h-6 w-6">...</svg>
    <div class="flex-1">
      <h3 class="font-bold">Success!</h3>
      <div class="text-sm">Operation completed successfully</div>
    </div>
  </div>
</div>

<!-- Error alert -->
<div class="alert alert-error">...</div>

<!-- Warning alert -->
<div class="alert alert-warning">...</div>

<!-- Info alert -->
<div class="alert alert-info">...</div>

Real-world Alert Component

notification.component.ts
getAlertClass(): string {
  switch (this.notification().type) {
    case NotificationType.SUCCESS:
      return 'alert-success';
    case NotificationType.ERROR:
      return 'alert-error';
    case NotificationType.WARNING:
      return 'alert-warning';
    case NotificationType.INFO:
      return 'alert-info';
    default:
      return 'alert-info';
  }
}
notification.component.html
<div class="alert {{ getAlertClass() }} shadow-lg mb-2 animate-in slide-in-from-top">
  <div class="flex items-center gap-2">
    <svg class="stroke-current shrink-0 h-6 w-6">...</svg>
    <div class="flex-1">
      @if (notification().title) {
        <h3 class="font-bold">{{ notification().title }}</h3>
      }
      <div class="text-sm">{{ notification().message }}</div>
    </div>
    <button class="btn btn-sm btn-circle btn-ghost" (click)="dismiss()">
      <svg class="h-4 w-4">...</svg>
    </button>
  </div>
</div>
<ul class="menu w-full min-w-10 flex overflow-x-auto whitespace-nowrap bg-base-200 p-2">
  @for (menuoption of menu(); track menuoption.id) {
    <li><a>{{ menuoption.label }}</a></li>
  }
</ul>
<div class="navbar bg-base-200 items-end">
  <div class="flex-1">
    <a class="btn btn-ghost text-xl">Happy Habitat</a>
  </div>
  <div class="flex-none">
    <!-- Navigation items -->
  </div>
</div>

Loading Indicators

<!-- Spinner -->
<span class="loading loading-spinner loading-sm"></span>
<span class="loading loading-spinner loading-md"></span>
<span class="loading loading-spinner loading-lg"></span>

<!-- Dots -->
<span class="loading loading-dots"></span>

<!-- Ring -->
<span class="loading loading-ring"></span>

Join (Button Groups)

Used for pagination and grouped controls:
<div class="join text-center">
  <button class="join-item btn"><i class="fa-solid fa-angles-left"></i></button>
  <button class="join-item btn"><i class="fa-solid fa-angle-left"></i></button>
  <button class="join-item btn">{{ currentPage() }}</button>
  <button class="join-item btn btn-disabled">...</button>
  <button class="join-item btn btn-active">{{ totalPages() }}</button>
  <button class="join-item btn"><i class="fa-solid fa-angle-right"></i></button>
  <button class="join-item btn"><i class="fa-solid fa-angles-right"></i></button>
</div>

Dividers

<!-- Horizontal divider -->
<div class="divider">OR</div>

<!-- Vertical divider -->
<div class="divider divider-vertical"></div>

<!-- Simple divider -->
<div class="divider my-6"></div>
<a class="link link-primary">Primary link</a>
<a class="link link-hover">Hover link</a>

Modals

DaisyUI modals are used for dialogs and confirmations:
<!-- Modal trigger -->
<button onclick="my_modal.showModal()">Open Modal</button>

<!-- Modal dialog -->
<dialog id="my_modal" class="modal">
  <div class="modal-box">
    <h3 class="text-3xl font-bold">Modal Title</h3>
    <p class="py-4">Modal content goes here</p>
    <div class="modal-action">
      <form method="dialog">
        <button class="btn">Close</button>
      </form>
    </div>
  </div>
</dialog>

Responsive Modal

<dialog id="deleteModal" class="modal modal-bottom sm:modal-middle">
  <div class="modal-box">
    <h3 class="font-bold text-lg">Confirm Delete</h3>
    <p class="py-4">Are you sure you want to delete this item?</p>
    <div class="modal-action">
      <form method="dialog">
        <button class="btn btn-error">Delete</button>
        <button class="btn">Cancel</button>
      </form>
    </div>
  </div>
  <form method="dialog" class="modal-backdrop">
    <button>close</button>
  </form>
</dialog>

Tables

Tables display structured data with zebra striping:
<table class="table table-zebra w-full">
  <thead>
    <tr>
      <th>Name</th>
      <th>Email</th>
      <th>Status</th>
      <th>Actions</th>
    </tr>
  </thead>
  <tbody>
    @for (item of items; track item.id) {
      <tr>
        <td>{{ item.name }}</td>
        <td>{{ item.email }}</td>
        <td>
          <span class="badge badge-success">Active</span>
        </td>
        <td>
          <button class="btn btn-ghost btn-sm">Edit</button>
        </td>
      </tr>
    }
  </tbody>
</table>

Table Variants

<!-- Zebra striping -->
<table class="table table-zebra">...</table>

<!-- Compact -->
<table class="table table-compact">...</table>

<!-- Pinned rows -->
<table class="table table-pin-rows">...</table>

Badges

Badges display labels and status indicators:
<!-- Basic badges -->
<span class="badge">Default</span>
<span class="badge badge-primary">Primary</span>
<span class="badge badge-secondary">Secondary</span>

<!-- Badge sizes -->
<span class="badge badge-lg">Large</span>
<span class="badge badge-sm">Small</span>
<span class="badge badge-xs">Tiny</span>

<!-- Badge with outline -->
<span class="badge badge-outline">Outline</span>
<span class="badge badge-lg badge-outline">Large Outline</span>

<!-- Status badges -->
<span class="badge bg-red-500 badge-sm text-white">Charge</span>
<span class="badge bg-green-500 badge-sm text-white">Payment</span>
<span class="badge badge-sm bg-purple-500 text-white border-0">Pending</span>

Tooltips

Tooltips provide contextual information on hover:
<!-- Basic tooltip -->
<span class="tooltip" data-tip="Tooltip text">
  Hover me
</span>

<!-- Tooltip positions -->
<span class="tooltip tooltip-top" data-tip="Top tooltip">Top</span>
<span class="tooltip tooltip-bottom" data-tip="Bottom tooltip">Bottom</span>
<span class="tooltip tooltip-left" data-tip="Left tooltip">Left</span>
<span class="tooltip tooltip-right" data-tip="Right tooltip">Right</span>

<!-- Responsive tooltip (shown on mobile) -->
<span class="tooltip tooltip-bottom sm:block lg:hidden" 
      [attr.data-tip]="'Emergency button'">
  <svg>...</svg>
</span>

Semantic Color System

DaisyUI provides semantic color classes that adapt to themes:

Background Colors

  • bg-base-100 - Primary background
  • bg-base-200 - Secondary background (navbar, menu)
  • bg-base-300 - Tertiary background
  • bg-primary - Primary brand color
  • bg-secondary - Secondary brand color
  • bg-accent - Accent color
  • bg-neutral - Neutral color

Text Colors

  • text-base-content - Main text color
  • text-primary - Primary text
  • text-secondary - Secondary text
  • text-accent - Accent text

State Colors

  • bg-success / text-success - Success state
  • bg-error / text-error - Error state
  • bg-warning / text-warning - Warning state
  • bg-info / text-info - Info state

Component Modifiers

Size Modifiers

Most components support size variants:
<!-- Extra small -->
<button class="btn btn-xs">XS</button>

<!-- Small -->
<button class="btn btn-sm">SM</button>

<!-- Normal (default) -->
<button class="btn">Normal</button>

<!-- Large -->
<button class="btn btn-lg">LG</button>

Style Modifiers

<!-- Ghost (transparent background) -->
<button class="btn btn-ghost">Ghost</button>

<!-- Outline -->
<button class="btn btn-outline">Outline</button>

<!-- Active state -->
<button class="btn btn-active">Active</button>

<!-- Disabled state -->
<button class="btn btn-disabled">Disabled</button>

Shape Modifiers

<!-- Circle -->
<button class="btn btn-circle">X</button>

<!-- Square -->
<button class="btn btn-square">â–¡</button>

Best Practices

1. Use Semantic Classes

Prefer semantic DaisyUI classes over Tailwind utility combinations:
<!-- Good -->
<button class="btn btn-primary">Submit</button>

<!-- Avoid -->
<button class="px-4 py-2 bg-blue-600 text-white rounded-lg hover:bg-blue-700">
  Submit
</button>

2. Consistent Form Structure

Always wrap form elements in form-control:
<div class="form-control w-full mb-4">
  <label class="label">
    <span class="label-text">Field Label</span>
  </label>
  <input class="input input-bordered w-full" />
</div>

3. State Management

Use Angular’s reactive features with DaisyUI classes:
<input
  class="input input-bordered"
  [class.input-error]="!isValid"
  [class.input-success]="isValid"
/>

4. Theme-Aware Colors

Always use semantic color classes for theme compatibility:
<!-- Good: Theme-aware -->
<div class="bg-base-100 text-base-content">Content</div>

<!-- Avoid: Hard-coded colors -->
<div class="bg-white text-black">Content</div>

5. Component Composition

Combine DaisyUI components with Tailwind utilities:
<div class="card bg-base-100 shadow-xl max-w-md mx-auto">
  <div class="card-body">
    <!-- DaisyUI card with Tailwind utilities for sizing and centering -->
  </div>
</div>

Common Patterns

Login Form Pattern

<div class="w-full px-4">
  <div class="card w-full max-w-md bg-base-100 shadow-xl">
    <div class="card-body">
      <h2 class="card-title justify-center text-2xl mb-4">Login</h2>
      
      <form [formGroup]="loginForm" (ngSubmit)="onSubmit()">
        <div class="form-control w-full mb-4">
          <label class="label">
            <span class="label-text">Username</span>
          </label>
          <input
            type="text"
            class="input input-bordered w-full"
            formControlName="username"
          />
        </div>
        
        <div class="form-control mt-6">
          <button type="submit" class="btn btn-primary w-full">
            @if (isLoading()) {
              <span class="loading loading-spinner loading-sm"></span>
              Loading...
            } @else {
              Login
            }
          </button>
        </div>
      </form>
    </div>
  </div>
</div>

Notification Pattern

See Notification Component above for implementation.

Pagination Pattern

See Join (Button Groups) above for implementation. The panic button modal demonstrates a complete modal implementation:
panic-button.component.html
<li class="flex justify-center py-1 m-0 bg-red-500 rounded-full text-bold text-white">
  <div class="w-full rounded-full" onclick="panic_modal.showModal()">
    <span class="relative group">
      <span class="hidden lg:inline text-2xl">
        <svg>...</svg>
      </span> 
      <span class="tooltip tooltip-bottom sm:block lg:hidden" 
            [attr.data-tip]="'Emergency button'">
        <svg>...</svg>
      </span>
    </span>
    <span class="hidden lg:inline">{{ 'Alarm' }}</span>        
  </div>
</li>

<dialog id="panic_modal" class="modal">
  <div class="modal-box">
    <h3 class="text-3xl font-bold text-red-700">Alarm</h3>
    <p class="py-4">An alarm will be sent to the security booth</p>
    <p class="py-4">From house XXXX at XX:XX</p>
    <p class="py-4 text-xs text-bold">
      Any misuse of the alarm button will be sanctioned according to regulations.
    </p>
    <div class="modal-action">
      <form method="dialog">
        <button class="btn bg-red-500 text-white mr-5">Send alarm!</button>
        <button class="btn">Cancel</button>
      </form>
    </div>
  </div>
</dialog>

Table Pattern

Tables are commonly used for listing data with actions:
<table class="table table-zebra w-full">
  <thead>
    <tr>
      <th>Date</th>
      <th>Type</th>
      <th>Description</th>
      <th>Amount</th>
      <th>Balance</th>
    </tr>
  </thead>
  <tbody>
    @for (transaction of transactions(); track transaction.id) {
      <tr>
        <td>{{ transaction.date | date }}</td>
        <td>
          @if (transaction.type === 'charge') {
            <span class="badge bg-red-500 badge-sm text-white">Charge</span>
          } @else {
            <span class="badge bg-green-500 badge-sm text-white">Payment</span>
          }
        </td>
        <td>{{ transaction.description }}</td>
        <td>{{ transaction.amount | currency }}</td>
        <td>{{ transaction.balance | currency }}</td>
      </tr>
    }
  </tbody>
</table>

Customization

Custom Theme Colors

To customize theme colors, you would typically extend the DaisyUI configuration. With Tailwind v4, this is done in CSS:
@plugin "daisyui" {
  themes: [
    {
      mytheme: {
        primary: "#a991f7",
        secondary: "#f6d860",
        accent: "#37cdbe",
        neutral: "#3d4451",
        "base-100": "#ffffff",
      },
    },
  ];
}

Resources

Next Steps

TailwindCSS

Learn about TailwindCSS utility classes

Component Library

Explore custom Angular components

Build docs developers (and LLMs) love