Light / Dark Theme
Tema claro/escuro para todas as UIs Koder (web, Flutter mobile/desktop, TV, landing pages): comportamento padrão pós-instalação (ThemeMode.system), persistência da escolha do usuário, anti-flash, CSS vars.
Semantic color tokens
Every Koder UI exposes the same semantic CSS variables. Pages declare the light values on :root and override them under [data-theme="dark"]. The exact values below are the canonical baseline; products may tighten them.
--bg #ffffff #0b1120 page background--text #0f172a #f1f5f9 primary text--text-muted #475569 #94a3b8 secondary text--surface #f8fafc #111827 card / panel surface--surface-2 #e2e8f0 #1f2937 raised surface--accent #1d4ed8 #60a5fa primary action--accent-on #ffffff #0b1120 text on accent--border #cbd5e1 #334155 divider / border--focus #1e3a8a #bfdbfe focus ringRequired behavior
- Two modes only — Light and Dark — no third "system" toggle position.
- Initial selection — Honor the OS prefers-color-scheme on first load (or after localStorage is cleared).
- User choice persistence — Save the explicit toggle to localStorage["theme"]; this overrides the OS preference on later visits.
- Live OS propagation — When no user preference is saved, follow OS changes via matchMedia(...).addEventListener("change", …).
- No flash of wrong theme — Apply the saved theme inline before the first render, in <head> before the CSS link.
Required CSS structure
:root {
--bg: #ffffff;
--text: #0f172a;
/* ... other semantic tokens ... */
color-scheme: light;
}
[data-theme="dark"] {
--bg: #0b1120;
--text: #f1f5f9;
/* ... */
color-scheme: dark;
}Anti-flash inline script
Place this snippet inside <head>, before any external stylesheet. It is the smallest correct implementation.
<script>
(function(){
const s = localStorage.getItem('theme');
const dark = s ? s === 'dark' : matchMedia('(prefers-color-scheme:dark)').matches;
if (dark) document.documentElement.setAttribute('data-theme','dark');
})();
</script>Required toggle JavaScript
function toggleTheme() {
const isDark = document.documentElement.getAttribute('data-theme') === 'dark';
const next = isDark ? 'light' : 'dark';
localStorage.setItem('theme', next);
applyTheme();
}
function applyTheme() {
const saved = localStorage.getItem('theme');
const isDark = saved
? saved === 'dark'
: matchMedia('(prefers-color-scheme:dark)').matches;
document.documentElement.setAttribute('data-theme', isDark ? 'dark' : 'light');
}
matchMedia('(prefers-color-scheme:dark)').addEventListener('change', () => {
if (!localStorage.getItem('theme')) applyTheme();
});
applyTheme();Flutter / native apps
Native apps follow the same contract. Until KoderTheme widget ships in koder_kit v0.6.0, use the inline pattern below — and never hardcode ThemeMode.dark or ThemeMode.light before reading the saved preference.
// main.dart — until KoderTheme widget ships in koder_kit v0.6.0+
Future<void> main() async {
WidgetsFlutterBinding.ensureInitialized();
final prefs = await SharedPreferences.getInstance();
final saved = prefs.getString('theme'); // 'light' | 'dark' | null
runApp(MyApp(initialTheme: saved));
}
ThemeMode _resolve(String? saved) {
if (saved == 'dark') return ThemeMode.dark;
if (saved == 'light') return ThemeMode.light;
return ThemeMode.system; // follow OS when no preference saved
}Audit checklist
/k-housekeep and equivalent linters verify each web surface meets every item.
- <head> contains the anti-flash script that reads localStorage["theme"] and sets data-theme.
- CSS contains a [data-theme="dark"] selector with overrides.
- color-scheme: light on :root and color-scheme: dark on [data-theme="dark"].
- A toggle button referencing toggleTheme() (or equivalent) lives in the navbar.
- JavaScript exposes both toggleTheme and applyTheme functions with the documented behavior.
- Two icons (sun / moon) swap visibility when the toggle fires.
- matchMedia('(prefers-color-scheme:dark)') is used and reacted to.
- Saved values are exactly "light" or "dark" — never any other string.