Tema claro / escuro
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.
Tokens semânticos de cor
Toda UI Koder expõe as mesmas variáveis CSS semânticas. As páginas declaram os valores light em :root e sobrescrevem em [data-theme="dark"]. Os valores abaixo são a baseline canônica; produtos podem tighten.
--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 ringComportamento obrigatório
- Apenas dois modos — Claro e Escuro — sem terceiro estado "sistema" no toggle.
- Seleção inicial — Honrar prefers-color-scheme do SO no primeiro load (ou após localStorage limpo).
- Persistência da escolha do usuário — Salvar o toggle explícito em localStorage["theme"]; isso sobrescreve a preferência do SO em visitas futuras.
- Propagação live do SO — Sem preferência salva, seguir mudanças do SO via matchMedia(...).addEventListener("change", …).
- Sem flash de tema errado — Aplicar o tema salvo inline antes do primeiro render, no <head> antes do link de CSS.
Estrutura CSS obrigatória
:root {
--bg: #ffffff;
--text: #0f172a;
/* ... other semantic tokens ... */
color-scheme: light;
}
[data-theme="dark"] {
--bg: #0b1120;
--text: #f1f5f9;
/* ... */
color-scheme: dark;
}Script inline anti-flash
Coloque este snippet dentro do <head>, antes de qualquer stylesheet externo. É a menor implementação correta.
<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>JavaScript do toggle
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();Apps Flutter / nativos
Apps nativos seguem o mesmo contrato. Até o widget KoderTheme aparecer no koder_kit v0.6.0, use o pattern inline abaixo — e nunca hardcode ThemeMode.dark ou ThemeMode.light antes de ler a preferência salva.
// 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
}Checklist de auditoria
/k-housekeep e linters equivalentes verificam que cada surface web cumpre todos os itens.
- <head> contém o script anti-flash que lê localStorage["theme"] e seta data-theme.
- CSS contém um seletor [data-theme="dark"] com overrides.
- color-scheme: light em :root e color-scheme: dark em [data-theme="dark"].
- Um botão toggle referenciando toggleTheme() (ou equivalente) vive na navbar.
- JavaScript expõe as funções toggleTheme e applyTheme com o comportamento documentado.
- Dois ícones (sol / lua) trocam de visibilidade quando o toggle dispara.
- matchMedia('(prefers-color-scheme:dark)') é usado e reagido.
- Valores salvos são exatamente "light" ou "dark" — nunca outra string.