Contributing
What a component is made of, and where each piece lives.
A component is five pieces, each in its own place. None of them is optional, and the order is the order they are written in.
packages/themes/src/presets/base/components/toggleswitch.ts # tokens
packages/styles/src/toggleswitch/{toggleswitch.css,index.ts} # CSS + class map
packages/vue/src/components/ToggleSwitch/
types.ts # the props interface, extending BaseProps
ToggleSwitch.vue # the component
ToggleSwitch.spec.ts# axe + keyboard + ARIA
index.ts
apps/docs/src/demos/ToggleSwitch.vue # the demo page1. Tokens
A token tree whose values refer to the semantic layer ('{formField.borderColor}', '{control.minHeight}') rather than to a colour. Anything that differs between schemes goes under colorScheme. Every token becomes a --vt-toggleswitch-… variable.
2. Style
- Classes are
vt-toggleswitch,vt-toggleswitch-<part>,vt-toggleswitch-<modifier>. - CSS reads only
var(--vt-…). A hard-coded colour, radius or duration is a bug. - Build on the shared chrome (
.vt-field,.vt-overlay,.vt-option,.vt-mask) rather than restyling it. - Keyboard focus shows the focus ring; anything that animates stops under
prefers-reduced-motion.
3. The component
<script setup lang="ts">
defineOptions({ name: 'VtToggleSwitch' });
const props = withDefaults(defineProps<ToggleSwitchProps>(), {
unstyled: undefined // required: an absent boolean prop would be false,
}); // and false would override the global setting
const { part } = useComponent(toggleswitchStyle, props);
</script>
<template>
<!-- one call per element: theme classes, pass-through and state -->
<div v-bind="part('root', state)">
<span v-bind="part('thumb', state)" />
</div>
</template>v-bind="part(…)" on every element is what makes a component themeable, reachable by pass-through and usable unstyled. A component wrapping a native control also uses useSplitAttrs() with inheritAttrs: false, so class dresses the wrapper while name, aria-describedby and the rest reach the control.
4. The demo
A page in apps/docs/src/demos/ exporting meta: DemoMeta from a plain <script> block. This site finds it by glob, and reads the same file again as text for the code beside each example, so a snippet cannot drift from what it documents.
5. Barrels
pnpm gen writes every index.ts list. Nobody edits them by hand.
Conventions worth knowing
v-modelthroughdefineModel(); ids from Vue'suseId().- Text comes from the locale, never from a literal in the template.
- Popups use
<Teleport to="body">, thevt-overlaytransition anduseOverlay(); modals adduseFocusTrap()andlockScroll(). - Behaviour with no DOM in it (navigation, parsing, selection) goes to
@vitral/core, with a test there. - Reach for an option before a new component: two things that differ only in what they refuse are one thing with a prop.
Checks
pnpm test # vitest + axe
pnpm typecheck # vue-tsc over every package and the site
node scripts/gen-index.mjs --check # barrels up to date
pnpm build # every package builds, with declarations