LibyUI Logo

Mode Toggle

Light, dark, and system theme switcher built with next-themes, Base UI Dropdown, and Tooltip.

Overview

The Mode Toggle is a composite component that lets users switch between light, dark, and system themes. It combines a Base UI DropdownMenu, a Tooltip, and the LibyUI Button — all animated with CSS transitions.

It is called "composite" because it orchestrates multiple primitives into a single, cohesive interaction pattern.

Usage

import { ModeToggle } from "@/components/composite/mode-toggle";

export default function Navbar() {
  return (
    <header className="flex items-center justify-between px-4 py-2">
      <span>My App</span>
      <ModeToggle />
    </header>
  );
}

Source

import * as React from 'react'
import { Moon, Sun } from 'lucide-react'
import { useTranslations } from 'next-intl'
import { useTheme } from 'next-themes'

import { Button } from '@/components/ui/button'
import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger } from '@/components/ui/dropdown-menu'
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from '@/components/ui/tooltip'

export function ModeToggle() {
    const { setTheme } = useTheme()
    const t = useTranslations('THEME')

    return (
        <TooltipProvider>
            <Tooltip>
                <DropdownMenu>
                    <DropdownMenuTrigger asChild>
                        <TooltipTrigger asChild>
                            <Button
                                variant="ghost"
                                size="icon"
                            >
                                <Sun className="h-[1.2rem] w-[1.2rem] scale-100 rotate-0 transition-all dark:scale-0 dark:-rotate-90" />
                                <Moon className="absolute h-[1.2rem] w-[1.2rem] scale-0 rotate-90 transition-all dark:scale-100 dark:rotate-0" />
                                <span className="sr-only">{t('TOGGLE_THEME')}</span>
                            </Button>
                        </TooltipTrigger>
                    </DropdownMenuTrigger>
                    <DropdownMenuContent align="end">
                        <DropdownMenuItem onClick={() => setTheme('light')}>{t('LIGHT')}</DropdownMenuItem>
                        <DropdownMenuItem onClick={() => setTheme('dark')}>{t('DARK')}</DropdownMenuItem>
                        <DropdownMenuItem onClick={() => setTheme('system')}>{t('SYSTEM')}</DropdownMenuItem>
                    </DropdownMenuContent>
                </DropdownMenu>
                <TooltipContent>
                    <p>{t('TOGGLE_THEME')}</p>
                </TooltipContent>
            </Tooltip>
        </TooltipProvider>
    )
}

Props

PropTypeDefaultDescription
ModeToggle accepts no props. Theme state is managed internally via next-themes.

Dependencies

PackagePurpose
next-themesProvides useTheme() for reading and setting the active theme
next-intlProvides useTranslations() for i18n labels (LIGHT, DARK, SYSTEM, TOGGLE_THEME)
lucide-reactSun and Moon icons with CSS transition animations

Accessibility

  • The trigger Button contains a visually hidden <span className="sr-only"> that announces the toggle action to screen readers.
  • The Tooltip provides a visible label on hover and focus.
  • The DropdownMenu is fully keyboard-navigable (Arrow keys, Enter, Escape).

Have feedback on the Mode Toggle or want a variant without i18n dependency? Let us know.

How is this guide?

Last updated on 2/25/2026