An optional look you switch on with one attribute, built from three variables the components already read.

Last updated

A skin changes how controls are drawn without touching a component. The base theme defines three inert variables; a skin file redefines them under [data-skin="<name>"]. Put the attribute on <html> for the whole site or on any element for a subtree, and remove it to go back. The docs navbar has a skin switch next to the theme switch, so you can preview every page both ways.

Variable Default Read by
--shadow-control none solid and outline Buttons, pressed ToggleButtons, the selected SegmentedControl chip
--highlight-control transparent the inner top highlight of solid Buttons
--shadow-field none outlined text fields

Skeuomorphic

The first skin: a bevel and a top sheen on buttons and checked controls, inset wells for fields and unchecked boxes, and a lit edge with a soft shadow on surfaces (cards, choice cards, menus, popovers, dialogs, toasts, featured icons). Both themes are tuned separately.

A card picks up the lit edge.

Show code
<div data-skin="skeuomorphic">
<Button>Save</Button>
<Button variant="outline" color="neutral">Cancel</Button>
<Input label="Email" placeholder="you@example.com" />
<SegmentedControl name="view" options={[{ value: 'list', label: 'List' }, { value: 'grid', label: 'Grid' }]} value="list" />
</div>

The demo above follows the navbar switch. A subtree can opt in on its own:

Show code
<Button>Plain page</Button>
<div data-skin="skeuomorphic"><Button>Skinned region</Button></div>

Enable it

npx @ava-ui/apogee init --skin skeuomorphic   # writes src/styles/apogee-skin-skeuomorphic.css

Or copy registry/tokens/skin-skeuomorphic.css beside your tokens file. Then:

@import "./apogee.css";
@import "./apogee-skin-skeuomorphic.css";
<html data-skin="skeuomorphic">

Switch at runtime with the helpers in utils/theme.ts; the choice persists under apogee-skin and apogee:skin-change fires on document:

import { getSkin, setSkin } from '@/components/apogee/utils/theme.ts'
setSkin('skeuomorphic')   // on
setSkin(null)             // off
getSkin()                 // 'skeuomorphic' | null

Restore it before paint the same way as the theme:

<script is:inline>
  const s = localStorage.getItem('apogee-skin')
  if (s) document.documentElement.dataset.skin = s
</script>

Writing a skin

A skin is a CSS file that sets the three variables under [data-skin='<name>'], with a second block for dark mode. It must not touch component classes; if a look needs more than the variables, add a variable to the base theme first so every skin can use it.

Do / Don’t

  • Do apply it page-wide or to a whole section; mixed controls side by side look like two products.
  • Do keep the alphas: the values are ink over any fill, so they work on every hue.
  • Don’t nest skins; the inner attribute wins and the seam shows.
  • Don’t rely on a skin for contrast: the contrast gate measures the base tokens only.