---
import Select from './Select.astro';
---
<starlight-theme-select>
{/* TODO: Can we give this select a width that works well for each language’s strings? */}
<Select
icon="laptop"
label={Astro.locals.t('themeSelect.accessibleLabel')}
value="auto"
options={[
{ label: Astro.locals.t('themeSelect.dark'), selected: false, value: 'dark' },
{ label: Astro.locals.t('themeSelect.light'), selected: false, value: 'light' },
{ label: Astro.locals.t('themeSelect.auto'), selected: true, value: 'auto' },
]}
width="6.25em"
/>
</starlight-theme-select>
{/* Inlined to avoid FOUC. Uses global scope from `ThemeProvider.astro` */}
<script is:inline>
StarlightThemeProvider.updatePickers();
</script>
<script>
type Theme = 'auto' | 'dark' | 'light';
/** Key in `localStorage` to store color theme preference at. */
const storageKey = 'starlight-theme';
/** Get a typesafe theme string from any JS value (unknown values are coerced to `'auto'`). */
const parseTheme = (theme: unknown): Theme =>
theme === 'auto' || theme === 'dark' || theme === 'light' ? theme : 'auto';
/** Load the user’s preference from `localStorage`. */
const loadTheme = (): Theme =>
parseTheme(typeof localStorage !== 'undefined' && localStorage.getItem(storageKey));
/** Store the user’s preference in `localStorage`. */
function storeTheme(theme: Theme): void {
if (typeof localStorage !== 'undefined') {
localStorage.setItem(storageKey, theme === 'light' || theme === 'dark' ? theme : '');
}
}
/** Get the preferred system color scheme. */
const getPreferredColorScheme = (): Theme =>
matchMedia('(prefers-color-scheme: light)').matches ? 'light' : 'dark';
/** Update select menu UI, document theme, and local storage state. */
function onThemeChange(theme: Theme): void {
StarlightThemeProvider.updatePickers(theme);
document.documentElement.dataset.theme = theme === 'auto' ? getPreferredColorScheme() : theme;
storeTheme(theme);
}
// React to changes in system color scheme.
matchMedia(`(prefers-color-scheme: light)`).addEventListener('change', () => {
if (loadTheme() === 'auto') onThemeChange('auto');
});
class StarlightThemeSelect extends HTMLElement {
constructor() {
super();
onThemeChange(loadTheme());
this.querySelector('select')?.addEventListener('change', (e) => {
if (e.currentTarget instanceof HTMLSelectElement) {
onThemeChange(parseTheme(e.currentTarget.value));
}
});
}
}
customElements.define('starlight-theme-select', StarlightThemeSelect);
</script>