Theme Disclosure
Expand appearance choices within the page layout.
Preview
Examples
Theme transition
A slide transition declared on this control. Duration is 500 milliseconds. Other controls keep their own settings.
Opens upward
Place the choices above the trigger and the label below it. Reserved space keeps the preview and trigger in place.
Icon with text
Show an icon beside the theme label.
When to use
- Show appearance choices in a settings panel with space in the expansion direction.
- Keep the choices open while people compare their selection.
- Use a bounded scroll area for a longer list.
When not to use
- Expansion would cover or move content in a compact toolbar. Use Theme Selector.
- You need a general content disclosure. This control selects a theme preference.
Installation
Set up uitheme-web in an existing Tailwind app. Then install this control. The floating selector is a separate component.
Usage
Place the control inside your theme provider. Expansion moves the content below it. The panel stays open after a selection. Its scroll area defaults to 240px and never exceeds 60% of the viewport height.
Set direction="up" to place the choices above the trigger. Leave space above
for the expanded panel. Anchor the control at the bottom of its container if the
trigger must stay in place. The Examples section shows this layout. The default
is direction="down".
Keep the label below the trigger when the panel opens upward. The component places the label there automatically. Keep the label above a downward-opening trigger. Preserve this order when you customize the layout.
Reserve enough height for the expanded panel, trigger, label, and frame padding.
Align downward controls to the top and upward controls to the bottom. The
examples reserve h-96 with the default maxHeight={240}. Adjust the space if
you change the scroll limit. Opening a panel must not shift its trigger or the
surrounding preview.
API reference
Use open with the change callback to control expansion. Use defaultOpen for
an initial expanded state. Use maxHeight to set the scroll limit.
| Prop | Type | Behavior |
|---|---|---|
label | string | Override the readable accessible label. |
class | string | Extend the root styles. Your classes replace default classes through the local merge helper. |
classes | object | Style root, trigger, content, item, and label parts. |
disabled | boolean | Disable interaction. Preference controls also wait for the provider to mount. |
animationType | ThemeAnimationType | Override the UI theme transition for this control. Defaults to the provider setting. |
duration | number | UI theme transition duration in milliseconds. Defaults to the provider setting. |
slideDirection | SlideDirection | Valid only with animationType set to ThemeAnimationType.SLIDE. Other animation types do not accept this prop. |
display | 'text' | 'icon-text' | Use text by default, or an icon with text. For an icon-only action, use Icon Theme Toggle. Light uses a sun, dark uses a moon, and system uses a monitor. |
open | boolean | Control whether the inline panel is expanded. |
defaultOpen | boolean | Start with the panel expanded. Defaults to false. |
update:open | event | Receive the next expanded state when the trigger is pressed. |
direction | 'down' | 'up' | Place choices below or above the trigger. Defaults to down. Use a bottom-anchored layout to keep an upward trigger in place. |
maxHeight | number | Limit the scroll area in pixels. Defaults to 240 and never exceeds 60% of the viewport height. |
Keyboard
| Key | Action |
|---|---|
Enter / Space | Expand or collapse the focused trigger. |
Tab | Move from the trigger into the expanded panel and its radio choices. |
Arrow keys | Change the selected radio within the group. |
Space | Select the focused radio. |
The panel stays open until you collapse it. Escape does not close an inline panel.
Accessibility
- Keep the visible label, trigger name, and radio group name.
- The trigger reports the expanded state. Hidden choices leave the tab order.
- The disabled state prevents expansion and selection.
- Keyboard controls use native buttons and radio inputs.
Theme behavior
The checked radio follows theme, including system. Choices come from themes. The color palette does not change.
The selected radio sets the transition origin. The library performs the page transition. Omitted transition props keep the provider settings.
AI Agent notes
- Import the local control. Use
uitheme-web/reactoruitheme-web/vuefor the runtime. - Read the configured provider choices. Do not invent theme values.
- Preserve the label, focus styles, selected state, disabled state, and scroll limit.
- Keep the label below an upward trigger and above a downward trigger.
- Reserve room for the full bounded panel, trigger, label, and frame padding.
- Anchor upward controls at the bottom and downward controls at the top. Opening the panel must not shift the preview.
- Use the separate floating selector in compact layouts.
- Keep page transitions in the library. Do not add a local transition helper.
- Check keyboard selection, preference persistence, and reduced motion in the running app.
Source
The CLI reuses the shared cn utility. It repairs a missing utility and checks an
existing utility before offering a replacement. This control installs one main
component file. It does not install the floating selector.
Dependencies: @lucide/vue
Customization
Use the classes prop to style the root, label, trigger, content, and items.
The frame uses theme tokens. Its outer radius equals the trigger radius plus
the frame padding. This control uses solid borders.
Set animationType and duration for the page transition. Set slideDirection
only with animationType="slide". Keep enough space in the expansion direction.

