Dark Mode
Sakana Element has full built-in dark mode support with a Catppuccin Mocha inspired color palette. The theme system provides three modes — light, dark, and system — with automatic localStorage persistence and system preference detection.
| Name | Category | Description | Type |
|---|---|---|---|
theme | Expose | Current theme setting | ComputedRef<'light' | 'dark' | 'system'> |
isDark | Expose | Whether dark mode is currently active | ComputedRef<boolean> |
prefersDark | Expose | Whether the OS prefers dark mode | Ref<boolean> |
prefers | Expose | The OS color scheme preference | Ref<'light' | 'dark'> |
setTheme | Method | Set theme to 'light', 'dark', or 'system' | (theme: Theme) => void |
toggleTheme | Method | Toggle between light and dark | () => void |
Basic Usage
Use the useTheme composable to toggle between themes. The state is shared across all components and persisted in localStorage.
<template>
<div class="demo-dark-mode">
<div class="demo-dark-mode__status">
<span>Theme: <code>{{ theme }}</code></span>
<span>isDark: <code>{{ isDark }}</code></span>
</div>
<div class="demo-dark-mode__actions">
<px-button @click="toggleTheme">Toggle Theme</px-button>
<px-button type="primary" @click="setTheme('light')">Light</px-button>
<px-button type="primary" @click="setTheme('dark')">Dark</px-button>
<px-button type="info" @click="setTheme('system')">System</px-button>
</div>
</div>
</template>
<script setup lang="ts">
import { useTheme } from 'sakana-element';
import { useData } from 'vitepress';
import { watch } from 'vue';
const { isDark, theme, toggleTheme, setTheme } = useTheme();
const { isDark: vpIsDark } = useData();
// Sync VitePress → useTheme (immediate: true aligns initial state)
watch(
vpIsDark,
(dark) => {
if (dark !== isDark.value) setTheme(dark ? 'dark' : 'light');
},
{ immediate: true },
);
// Sync useTheme → VitePress
watch(isDark, (dark) => {
if (dark !== vpIsDark.value) vpIsDark.value = dark;
});
</script>
<style scoped>
.demo-dark-mode__status {
display: flex;
gap: 20px;
margin-bottom: 16px;
font-size: 14px;
}
.demo-dark-mode__status code {
padding: 2px 6px;
border: 1px solid var(--px-border-color-lighter);
background: var(--px-fill-color-lighter);
}
.demo-dark-mode__actions {
display: flex;
flex-wrap: wrap;
gap: 10px;
}
</style>Theme Modes
The setTheme function accepts three values:
| Mode | Description |
|---|---|
'light' | Force light mode regardless of system preference |
'dark' | Force dark mode regardless of system preference |
'system' | Automatically follow the user's OS color scheme |
import { useTheme } from 'sakana-element'
const { setTheme, toggleTheme, isDark, theme } = useTheme()
// Set a specific mode
setTheme('dark')
// Toggle between light ↔ dark (ignores system)
toggleTheme()
// Read current state
console.log(theme.value) // 'light' | 'dark' | 'system'
console.log(isDark.value) // true | falsePersistence
When you call setTheme(), the chosen mode is saved to localStorage under the key px-theme. On page reload, the theme is automatically restored.
System Preference Detection
The useSystemTheme composable gives you read-only access to the user's OS color scheme. It updates reactively when the system preference changes.
<script setup lang="ts">
import { useSystemTheme } from 'sakana-element'
const { prefersDark } = useSystemTheme()
</script>
<template>
<p>System prefers dark: {{ prefersDark }}</p>
</template>When to use which?
Use useTheme when you want to control the theme (toggle, persist, apply CSS classes). Use useSystemTheme when you only need to read the OS preference without changing anything — for example, to show a "Your system is in dark mode" indicator.
How It Works
When dark mode is active, Sakana Element adds the px-dark class to the <html> element. All component styles reference CSS custom properties (variables) that are redefined under .px-dark:
:root → light mode variables (default)
.px-dark, .dark → dark mode variable overridesThis means the theme switch is pure CSS — no component re-renders needed.
Customizing Colors
You can override any CSS variable for either theme. Define your overrides in your app's global CSS:
<template>
<div class="demo-custom-vars">
<div class="demo-custom-vars__section">
<h4>Default Theme</h4>
<div class="demo-custom-vars__row">
<px-button type="primary">Primary</px-button>
<px-button type="success">Success</px-button>
<px-button type="warning">Warning</px-button>
<px-button type="danger">Danger</px-button>
</div>
</div>
<div class="demo-custom-vars__section custom-theme">
<h4>Custom Theme (overridden variables)</h4>
<div class="demo-custom-vars__row">
<px-button type="primary">Primary</px-button>
<px-button type="success">Success</px-button>
<px-button type="warning">Warning</px-button>
<px-button type="danger">Danger</px-button>
</div>
</div>
<div class="demo-custom-vars__code">
<code>.custom-theme { --px-color-primary: #e64553; ... }</code>
</div>
</div>
</template>
<script setup lang="ts">
</script>
<style scoped>
.demo-custom-vars {
display: flex;
flex-direction: column;
gap: 20px;
}
.demo-custom-vars__section {
padding: 16px;
border: 2px solid var(--px-border-color-lighter);
background: var(--px-bg-color);
}
.demo-custom-vars__section h4 {
margin: 0 0 12px;
font-size: 14px;
color: var(--px-text-color-primary);
}
.demo-custom-vars__row {
display: flex;
flex-wrap: wrap;
gap: 10px;
}
.demo-custom-vars__code {
padding: 10px 14px;
font-size: 12px;
background: var(--px-fill-color-lighter);
border: 1px solid var(--px-border-color-lighter);
}
.demo-custom-vars__code code {
color: var(--px-text-color-regular);
}
/* Custom theme overrides — this is what we're demonstrating */
.custom-theme {
--px-color-primary: #e64553;
--px-color-primary-dark: #c73a47;
--px-color-primary-light-3: #eb6a76;
--px-color-primary-light-5: #f08f98;
--px-color-primary-light-7: #f5b4ba;
--px-color-primary-light-8: #f8c7cc;
--px-color-primary-light-9: #fce3e5;
--px-color-success: #209fb5;
--px-color-success-dark: #1a8599;
--px-color-warning: #df8e1d;
--px-color-warning-dark: #bf7a19;
--px-color-danger: #d20f39;
--px-color-danger-dark: #b30d31;
}
</style>Available Variable Categories
| Category | Example Variables | Description |
|---|---|---|
| Primary | --px-color-primary, --px-color-primary-dark | Brand accent color |
| Success | --px-color-success, --px-color-success-dark | Success state color |
| Warning | --px-color-warning, --px-color-warning-dark | Warning state color |
| Danger | --px-color-danger, --px-color-danger-dark | Error/danger state color |
| Info | --px-color-info, --px-color-info-dark | Informational state color |
| Background | --px-bg-color, --px-bg-color-page, --px-bg-color-overlay | Surface and page colors |
| Text | --px-text-color-primary, --px-text-color-regular, etc. | Typography colors |
| Border | --px-border-color, --px-border-color-light, etc. | Border and divider colors |
| Fill | --px-fill-color, --px-fill-color-light, etc. | Fill and background accent colors |
TIP
Each semantic color (primary, success, etc.) has a -dark shade and -light-3 through -light-9 variants. Override these too for a consistent look across hover, disabled, and focus states.