What is daisyUI v5?

8 min read #Django #Tailwind CSS #daisyUI

Published: A practical guide to modern web development with Django

Introduction

You've probably noticed the dark mode toggle in the corner of this site. Click it and the whole page changes palette โ€” backgrounds, borders, buttons, badges โ€” without a flash, a reload, or a single line of JavaScript beyond flipping an attribute. That's daisyUI, a component library that plugs into Tailwind CSS and supplies the two things utilities alone don't: reusable component classes and a real theme system.

Version 5 is a rewrite aimed squarely at Tailwind v4. It's smaller, it's CSS-native, and it has no JavaScript dependencies at all.

The Problem daisyUI Solves

Tailwind gives you utilities, not components. That's the point โ€” but it means every project rebuilds the same things:

  • A button with focus rings, disabled states, and size variants
  • An input with a border that matches your palette
  • A card with consistent padding and heading spacing
  • A modal, a dropdown, a badge, a navbar

Each one is a pile of utilities repeated across templates. Change your mind about border radius and you're editing forty files. And theming is worse: bg-gray-100 text-gray-900 is baked into the markup, so a dark mode means a second set of classes everywhere.

The usual escape hatch is a component library from a JavaScript framework โ€” React, Vue, Svelte โ€” which brings a runtime, a build step, and a component model you may not want in a server-rendered app.

The daisyUI Approach

daisyUI sits between the two. It's a Tailwind plugin that adds semantic class names, backed by theme variables.

Core idea: one class describes a whole component; the theme decides what it looks like.

<!-- Tailwind utilities only -->
<button class="inline-flex items-center justify-center rounded-lg bg-blue-600
               px-4 py-2 text-sm font-medium text-white hover:bg-blue-700
               focus:outline-none focus:ring-2 focus:ring-blue-500
               disabled:cursor-not-allowed disabled:opacity-50">
  Save
</button>

<!-- The same thing with daisyUI -->
<button class="btn btn-primary">Save</button>

The second version isn't shorter because it hides something โ€” it's shorter because the component's anatomy lives in the library, and its colors come from the active theme. Swap winter for night and that button changes without touching the markup.

What v5 changed

daisyUI 5 is a substantial release, not a patch:

  1. Tailwind v4 only. It loads with @plugin from your CSS file โ€” no tailwind.config.js, no plugin array in JavaScript.
  2. Readable CSS variables. The old --p, --b1, --bc names became --color-primary, --color-base-100, --color-base-content, with oklch() values you can inspect and tweak in devtools.
  3. Effects as variables. --depth adds a subtle 3D shadow to buttons and controls; --noise adds a grain texture. Both are per-theme and off/on with a single value.
  4. New components. list, status, fieldset, label, filter, calendar, validator, and dock (a redesigned bottom navigation).
  5. Much smaller. The npm package is about 61% smaller than v4, and the full CDN build is 34 kB compressed โ€” because native CSS nesting and better generation mean only the classes you use end up in your stylesheet.
  6. Zero dependencies. It's a CSS plugin. There is nothing to hydrate.

There are breaking changes too: input-bordered, select-bordered, and textarea-bordered are gone, because those components now have borders by default (input-ghost removes them). Inputs also default to a 20rem width.

How This Site Uses daisyUI

This site vendors daisyUI into src/static/css/ so the Docker image never needs Node.js. The whole configuration is three lines inside input.css:

@plugin "./daisyui.mjs" {
  themes:
    winter --default,
    night --prefersdark,
    silk;
}

What's happening:

  • ./daisyui.mjs is a tiny shim that re-exports the vendored package, so @plugin can load it by path.
  • winter is the default theme.
  • night is the one used when the operating system prefers dark mode.
  • silk is available for the recipes section, which uses a warmer palette.

Nothing else is configured. There's no theme file, no JS object, no plugin options beyond the theme list.

The components in use

The markup reads like a description of the page:

<div class="navbar bg-base-100 border-b border-base-200">
  <a class="btn btn-ghost btn-sm font-mono">notes</a>
</div>

<div class="hero min-h-[55vh]">
  <div class="hero-content text-center">
    <h1 class="text-4xl sm:text-6xl font-black tracking-tight">โ€ฆ</h1>
  </div>
</div>

<div class="card bg-base-100 border border-base-200 shadow-xl">
  <div class="card-body p-6">
    <h3 class="card-title">Backend</h3>
    <span class="badge badge-ghost badge-sm">#Django</span>
  </div>
</div>

navbar, hero, card, card-body, card-title, badge, divider, swap, join, and breadcrumbs are all daisyUI. The Tailwind utilities around them โ€” max-w-3xl, text-4xl, gap-6 โ€” still do the layout, which is the intended division of labour: daisyUI owns the component, Tailwind owns the arrangement.

One leftover worth mentioning: the blog search input still carries input input-bordered from the v4 days. In v5 input-bordered no longer exists, so it's simply ignored โ€” input alone already has the border.

Theme switching without a framework

daisyUI ships a theme-controller class that switches themes from a checkbox or radio input. This site does the same job by hand, because it needs scoped themes: the recipes section is always silk, while the blog and home pages follow the user's choice.

<div id="page-root"
     data-theme="{{ current_theme|default:'winter' }}"
     class="bg-base-100 text-base-content transition-colors duration-300">
<!-- navbar toggle -->
<input type="checkbox"
       {% if current_theme == 'night' %}checked{% endif %}
       onchange="document.getElementById('page-root').dataset.theme =
                 this.checked ? 'night' : 'winter';
                 document.cookie = 'index_theme=' +
                 (this.checked ? 'night' : 'winter') + ';max-age=31536000;path=/';" />

A context processor reads the index_theme cookie, so Django renders the correct theme on the first byte โ€” no flash of the wrong palette while JavaScript boots.

Key Classes

Class Purpose Notes
btn Button base Add btn-primary, btn-ghost, btn-outline, btn-sm
input Text input Bordered by default in v5; input-ghost removes it
card / card-body / card-title Card structure Compose with utilities for layout
badge Small label badge-ghost, badge-primary, badge-sm
navbar Top navigation bar Just a flex container with sensible defaults
hero / hero-content Centered landing block Good for above-the-fold sections
divider Horizontal rule Works vertically too
swap / swap-rotate Icon toggle animation Used by the theme switch
join / join-item Attached button group Shared borders, no gaps
status Tiny state dot New in v5; five sizes
fieldset / label Form grouping New in v5; replaces hand-rolled wrappers
dock Bottom navigation New in v5; respects safe-area insets

daisyUI + Django

The pairing is almost frictionless because both sides are server-rendered and stateless.

/* input.css */
@import "tailwindcss";
@source "../../**/templates/**/*.html";

@plugin "./daisyui.mjs" {
  themes: winter --default, night --prefersdark, silk;
}
# context_processors.py
def theme(request):
    return {"current_theme": request.COOKIES.get("index_theme", "winter")}
<!-- base template -->
<div data-theme="{{ current_theme }}" class="bg-base-100 text-base-content">
  {% block content %}{% endblock %}
</div>

Flow: 1. The cookie is read on the request 2. The template renders data-theme into the wrapper 3. daisyUI's theme variables resolve for that subtree 4. Toggling writes the attribute and the cookie โ€” no round trip 5. A reload still renders the right theme, because the server already knew

No API endpoint, no session, no client-side store.

Real-World Use Cases

daisyUI is a strong fit for:

  • Server-rendered apps โ€” Django, Rails, Laravel: components are just class names
  • Admin and internal tools โ€” tables, forms, modals, and alerts out of the box
  • Marketing pages โ€” hero, card, stats, and footer cover most of it
  • Multi-theme products โ€” one markup tree, many palettes via data-theme
  • Prototypes that need to look finished โ€” semantic classes read well in review

It's a weaker fit when you have a highly bespoke visual identity: you'll spend your time overriding component defaults, and plain utilities may be less work. It's also not a replacement for a JS behaviour library โ€” daisyUI styles a dropdown, but something still has to open it.

Getting Started

  1. Use Tailwind v4. daisyUI 5 does not support v3.

  2. Add the plugin to your CSS:

@import "tailwindcss";
@plugin "daisyui";
  1. Pick your themes. The defaults are light and dark; name your own:
@plugin "daisyui" {
  themes: winter --default, night --prefersdark, silk;
}
  1. Set the theme on an element:
<html data-theme="winter">
  1. Write components, not utility soup. Reach for btn, card, badge, and input first, then use utilities for layout and one-off adjustments.

  2. Customize a theme by declaring it with the same name:

@plugin "daisyui/theme" {
  name: "silk";
  default: false;
  --color-primary: oklch(55% 0.3 240);
  --radius-box: 0.5rem;
  --depth: 1;
}

The Philosophy

There's a gap between "here are 400 utilities, assemble your own button" and "adopt a JavaScript framework and its component model". daisyUI fills it with CSS: semantic classes for structure, variables for appearance, and themes as a first-class concept rather than a find-and-replace.

v5 doubles down on that by becoming a proper Tailwind v4 plugin โ€” configured in the same file as everything else, emitting only what's used, and shipping no runtime. For a Django site that already renders HTML on the server, that's the whole appeal: the components arrive as markup, and the theme arrives as data.


This site was built with Django 6.0+, HTMX, Tailwind CSS v4, daisyUI 5, and SQLite. All blog posts are authored in the Django admin as Markdown, rendered to HTML on save. The CSS is built with the vendored Tailwind CLI into a committed output.css, and the Docker image only collects static files โ€” it never builds CSS.

Read more about the tech stack