@col/theme-switcher
Versie: 1.0.0 Toegevoegd: 2026-08-04
Herkomst
Geëxtraheerd uit growerportal2/src/components/theme-switcher.tsx: een
ghost icon-knop die het thema laat cyclen tussen licht, donker en systeem.
Waarvoor
Eén knop om het thema te wisselen, met een icoon dat het huidige thema
toont. Klikken cyclet light → dark → system → light. Past zowel in een
bovenbalk als naast @col/user-panel in een zijbalk — het is bewust een
losstaand component zonder mening over waar het staat.
Gebruik het niet als je meer dan drie themamodi nodig hebt, of een aparte knop per modus wilt — dit is bewust één knop die cyclet, geen keuzemenu.
Wat je app moet leveren
| Prop | Type | Betekenis |
|---|---|---|
theme |
'light' | 'dark' | 'system' |
Het huidige thema. Verplicht. |
onChange |
(theme: Theme) => void |
Aangeroepen met het volgende thema in de cyclus wanneer er geklikt wordt. Verplicht. |
labels |
{ light?, dark?, system? } |
Labels voor de title-tooltip. Zonder een label voor het volgende thema valt de tooltip terug op de themanaam zelf ("light", "dark", "system"). |
Dit component is controlled en volledig ontkoppeld van next-themes. De
bron gebruikt useTheme() uit next-themes en houdt een mounted-guard
aan om een hydration mismatch te voorkomen — de server weet het thema nog
niet (het zit in localStorage), de client na de eerste render wel. Zonder
die guard render je server en client verschillend en klaagt React erover.
Die guard hoort in de app die dit item installeert:
'use client'
import { useTheme } from 'next-themes'
import { useEffect, useState } from 'react'
import { ThemeSwitcher } from '@/components/nav/theme-switcher'
export function ThemeSwitcherStarter() {
const { theme, setTheme } = useTheme()
const [mounted, setMounted] = useState(false)
useEffect(() => setMounted(true), [])
if (!mounted) return null
return <ThemeSwitcher theme={(theme as 'light' | 'dark' | 'system') ?? 'system'} onChange={setTheme} />
}
Bestanden
| Bestand | Landt in | Soort |
|---|---|---|
theme-switcher.tsx |
components/nav/ |
beheerd |
Upstream shadcn-afhankelijkheden: button. Geen cssVars.
Gebruikt door
| App | Sinds versie | Opmerkingen |
|---|---|---|
| — | — | Nog geen consumers. growerportal2 is de bron van dit patroon en draait nog op zijn eigen kopie in src/components/theme-switcher.tsx, inclusief de next-themes-koppeling en mounted-guard die hier bewust niet in zitten. |
Bewuste keuzes
- Geen
next-themes, geenmounted-guard. Zie "Wat je app moet leveren" hierboven — dat is precies het soort domeinkennis (een specifieke theming-library, een hydration-workaround) dat niet in een herbruikbaar registry-item hoort. Dit component weet alleen wat het volgende thema is en welk icoon daarbij hoort. aria-labeltoont het huídige thema,titlehet vólgende. Dat lijkt tegenstrijdig maar is bewust: detitle-tooltip volgt de bron (die laat zien wat een klik gaat doen), terwijlaria-labelnodig is om in een test of met een screenreader te kunnen vaststellen welk icoon nu getoond wordt, zonder in de SVG te hoeven kijken.- Cyclus is vast (
light → dark → system → light), geen prop om de volgorde te wijzigen. Een instelbare volgorde is nooit gevraagd en voegt complexiteit toe aan een component dat juist zijn waarde ontleent aan hoe klein het is.
Bekende beperkingen
- Geen ondersteuning voor een aangepaste iconenset —
Sun/Moon/Monitoruitlucide-reactliggen vast. Wil je andere iconen, dan is dit component niet het juiste startpunt. - Geen toetsenbordsnelkoppeling; alleen een klikbare knop.