Theme Controller

The Theme Controller component provides a plethora of pre-built sub-components that can be used to build a custom theme switcher.

Tip

The ThemeController component is a Builder component rather than your standard Slotable ViewComponent.

This means that instead of the component providing slots, and handling where your custom components are inserted, the developer utilizes helper methods on the component to choose where the sub-components are rendered.

In the examples below, we use the HAML = syntax to output the rendered sub-components, whereas typical components use the HAML - syntax to "pass" the component to a slot.

Warning

To prevent a flash of content when the page loads with a non-default theme, add = theme_preload_script to your layout's <head> section. This inline script runs before the page renders to set the theme from localStorage.

See GitHub Issue #49 and the ThemeHelper API Docs for more details.

Theme Preview Icons

One of the most basic sub-components is the theme preview icon. It shows a visual representation of the theme using the base, primary, secondary, and accent colors as little dots inside of a rounded square.

Preview
Light
Dark
Synthwave
Retro
Cyberpunk
Wireframe
Code
= daisy_theme_controller do |theme_controller|
  .flex.flex-col.sm:flex-row.gap-4.items-start.sm:items-center
    - theme_controller.themes.each do |theme|
      .flex.flex-row.gap-2.items-center
        = theme_controller.build_theme_preview(theme)
        = theme.humanize

Theme Radio Inputs

When a theme radio input is selected, the page's theme will change to the value of the input.

Tip

You do not need to wire up any setTheme action. The ThemeController automatically watches its inputs for change events, saves the selection to localStorage, and keeps every other theme selector on the page (including the header switcher) in sync.

We provide a custom name of docs-radio-theme so these inputs form their own radio group, but selecting one still syncs everywhere.

Preview
Code
= daisy_theme_controller do |theme_controller|
  .flex.flex-col.sm:flex-row.gap-4.items-start.sm:items-center
    - theme_controller.themes.each do |theme|
      %label.flex.flex-row.gap-2.items-center
        = theme_controller.build_radio_input(theme, name: "docs-radio-theme")
        = theme.humanize

Theme Radio with Inline Preview

build_radio_input accepts a block, which it forwards to the radio. That lets you drop a preview swatch and label inside the radio's own label (its end slot), so the whole row is a single clickable control — no separate wrapping <label> needed.

Preview
Code
= daisy_theme_controller do |theme_controller|
  .flex.flex-col.gap-2.items-start
    - theme_controller.themes.each do |theme|
      = theme_controller.build_radio_input(theme, name: "docs-inline-theme") do |radio|
        - radio.with_end do
          = theme_controller.build_theme_preview(theme)
          %span.capitalize= theme.humanize

Switcher Dropdown (Builder)

build_switcher_dropdown renders a complete, working theme switcher in one line — a trigger button plus a menu with a color preview, name, and a checkmark on the active theme for every theme, all wired to the loco-theme controller. Pass label:, icon:, clear:, or a placement via css: to customize it. (Everything below is hand-rolled from the sub-components for comparison.)

Preview
Code
.flex.gap-6.items-start
  = daisy_theme_controller(themes: %w[light dark synthwave retro]) do |theme_controller|
    = theme_controller.build_switcher_dropdown(css: "dropdown-start", name: "docs-switcher")

  = daisy_theme_controller(themes: %w[light dark synthwave retro]) do |theme_controller|
    = theme_controller.build_switcher_dropdown(label: "Theme", clear: true, css: "dropdown-start", name: "docs-switcher-labeled")

Custom Switcher

You can also build a custom switcher by using all of the sub-components in combination.

Warning

Note that the daisy_theme_controller may need to be the parent of your custom switcher to ensure that it appropriately sets up the Stimulus controller.

In the example below, the dropdown doesn't render custom content when using the with_item helper, so the Stimulus controller wouldn't be applied if the order were switched.

Preview
Code
= daisy_theme_controller do |tc|
  = daisy_dropdown do |dropdown|
    - dropdown.with_button(css: "btn-error", icon: "swatch", title: "Themes", right_icon: "chevron-down")

    - tc.themes.each do |theme|
      - dropdown.with_item do
        = daisy_link(href: "#", css: "no-underline", html: { data: { action: "click->loco-theme#setTheme" } }) do
          = tc.build_radio_input(theme, name: "docs-custom-theme", css: "hidden peer")
          = tc.build_theme_preview(theme)
          = theme.humanize
          = hero_icon "check", css: "size-4 invisible peer-checked:visible"
Made with by Profoundry .
Copyright © 2023-2026 Profoundry LLC.
All rights reserved.