shadcn.io is not affiliated with official shadcn/ui
Theme Toggle Pill Banner Block for React
A theme pill for a writing app: a three-way toggle group (Light, System, Dark) seeded from the page's theme, with a label that says which one is on.
Installation
Overview
A theme pill for React with Base UI, a system-aware control rather than a sun-and-moon switch. A start-aligned capsule sits in a muted shell between the page's rails: a three-item toggle group, Light, System and Dark, each an icon button with its own name, and a label that says which is on. The choice starts from your page's theme (the dark class on the document, read after the first render); picking another moves the pressed item, rewrites the label and says so in a polite line. The pressed item is filled with the foreground colour, so the primary colour stays on your page's one action. The block changes nothing else: connect its pick function to your theme provider. It follows your theme. It's built from Guides and Toggle Group.
FAQ
How do I connect it to my theme provider?
pick is where a choice lands: it stores the theme and speaks the result. Add your provider's call there (setTheme(next) in next-themes, for example) and read the starting value from the provider instead of the dark class. System means "follow the device": your provider resolves it with the prefers-color-scheme media query.
Why does it read the theme in an effect?
The server doesn't know the visitor's theme, so the first render assumes light and an effect corrects it right after hydration. Reading the dark class during render would make the server and the browser disagree. If your provider can tell the server the theme (a cookie), pass it in as the initial state and drop the effect.
How is it named for screen readers?
The group is named "Theme" and its three items "Light", "System" and "Dark", each with its pressed state; the visible label beside them is decoration, so it isn't read a second time. A polite status line, empty when the page loads, speaks the choice ("Dark mode on.") when it changes.
What does the keyboard do?
Tab enters the group and the arrow keys move between the three items; Enter or Space picks the focused one. Picking the item that is already on does nothing, so the group never ends up with no choice.
How do I pin it to the bottom of the screen?
The pill renders in flow so the preview and a stack of blocks can see it. To float it, add fixed inset-x-4 bottom-4 z-50 mx-auto to the pill (the section named "Theme") and remove the block's wrapper padding if the pill lives on its own. Keep room for the home indicator with bottom-[max(1rem,env(safe-area-inset-bottom))].
How does it fit on a phone?
The label sits beside the toggle group while there's room and wraps under it on the narrowest screens, and the pill becomes a rounded card when it does. The three wordings share one grid cell, so the pill never changes width when the choice changes. In a right-to-left layout it mirrors.
Related
- Pro
Split Theme Preview Toggle Hero
Interactive demosA reverse split hero for a theme editor: two preview cards, one light and one dark, in a hairline frame beside a sun and moon switch, a Shine Button and an outline button.
- Pro
A language pill for a travel-guide site: a globe, "Guides in" and a button naming the current language, opening a popover of six languages written in their own scripts.
- Pro
Theme Picker Multi-Action Banner
Choices and promptsA corner-radius picker card for a design-token editor: four chips drawn with their own corners, a greyscale preview that re-rounds as you choose, and Apply and Reset actions.
- Pro
A dock pill for a workspace app: a toolbar of four icon buttons (New page, Search, Share, Notifications), each with a tooltip showing its name and its key.
- Pro
Reading Preferences Multi-Action Banner
Choices and promptsA reading-preferences prompt for a reading app: three toggles with a live preview that changes size, weight and border, a polite count, and Save, Reset and Not now.
- Pro
An install pill for a plant-care app: a greyscale app tile, the name, a row of stars with its score, a small Get link and a dismiss button that folds the block away.