Overview

themeAtom and titleAtom live on MatesUtils — access them with utils (created by renderApp).

themeAtom stores 'light' | 'dark' | 'auto', persists to localStorage['mates-theme'], keeps <html data-theme> in sync, and exposes resolved plus helpers (setToLight, setToDark, setToAuto, toggle).

titleAtom mirrors document.title via an internal effect — set it and the tab title updates; read it reactively in templates.

themeAtom vs lsStore

API
Key
Role
themeAtom mates-theme Dedicated theme chrome + DOM attribute sync.
lsStore mates_storage Generic JSON bag — not for theme helpers.

themeAtom helpers

Switch light / dark / auto and toggle. resolved is always light or dark.

import { html, useUtils, renderApp } from 'mates';

const App = () => {
const { themeAtom } = useUtils();

return () => html`
<div class="m-col m-gap">
<p class="muted">theme=${themeAtom()} · resolved=${themeAtom.resolved}</p>
<div class="m-flex m-items-center m-justify-center m-gap-sm">
<button class="btn-primary" @click=${() => themeAtom.setToLight()}>light</button>
<button @click=${() => themeAtom.setToDark()}>dark</button>
<button class="btn-ghost" @click=${() => themeAtom.setToAuto()}>auto</button>
<button @click=${() => themeAtom.toggle()}>toggle</button>
</div>
</div>
`;
};
renderApp(App, document.getElementById('app'));

titleAtom → document.title

Editing the input updates titleAtom and the browser tab title (inside the playground iframe).

import { html, useUtils, renderApp } from 'mates';

const App = () => {
const { titleAtom } = useUtils();
titleAtom.set('Playground');

return () => html`
<div class="m-col m-gap">
<p class="muted m-t-5">document.title ← titleAtom</p>
<input
.value=${titleAtom()}
@input=${(e) => titleAtom.set(e.target.value)}
placeholder="Tab title…"
/>
<p class="muted">${titleAtom()}</p>
</div>
`;
};
renderApp(App, document.getElementById('app'));