Templates
Templates are reusable code patterns with typed slots. Define them once, use them anywhere — the compiler expands them into full TypeScript.
Defining a template
A template has a name, typed slots, optional imports, and a body with {{ }} placeholders:
template name=zustand-store
slot name=storeName type=identifier
slot name=stateType type=identifier
import from=zustand names=create
body <<<
export const use{{storeName}}Store = create<{{stateType}}>((set, get) => ({
{{CHILDREN}}
}));
>>>Slot types
Each slot has a typed constraint that the compiler validates:
| Slot type | Validates | Example |
|---|---|---|
| identifier | Must match [A-Za-z_][A-Za-z0-9_]* | storeName, hookName |
| type | Non-empty TypeScript type | atomType, returnType |
| expr | Non-empty expression | cacheKey, queryFn |
| block | Any value (can be empty) | body content |
Slots can be optional with default values:
slot name=fetcher type=expr optional=true default=defaultFetcherUsing a template
Use a registered template by its name as a node type, passing slot values as props:
zustand-store storeName=Toast stateType=ToastState
handler <<<
toasts: [],
addToast: (msg) => set(s => ({
toasts: [...s.toasts, { id: Date.now().toString(), message: msg, type: 'info' }]
})),
removeToast: (id) => set(s => ({
toasts: s.toasts.filter(t => t.id !== id)
})),
>>>The handler <<< >>> block becomes the {{CHILDREN}} placeholder in the template body. The compiler:
- Looks up the template definition
- Validates required slots against the node's props
- Replaces
{{slotName}}placeholders with provided values - Replaces
{{CHILDREN}}with child node output - Prepends import statements
Built-in template catalog
kern init-templates scans your package.json and scaffolds .kern template files for detected libraries:
kern init-templates # Scans package.json -> scaffolds templatesLibrary templates
| Library | Template | What it generates |
|---|---|---|
| Zustand | zustand-store | Typed store with create<T>() |
| Zustand | zustand-selector | Typed selector function |
| SWR | swr-hook | Data fetching hook with useSWR |
| TanStack Query | query-hook | Query hook with useQuery |
| TanStack Query | mutation-hook | Mutation hook with useMutation |
| tRPC | trpc-query | tRPC route query hook |
| XState | xstate-machine | State machine with setup() |
| Jotai | jotai-atom | Typed atom with atom<T>() |
| Jotai | jotai-derived | Derived atom with get() |
Common templates (always included)
Two framework-agnostic templates are always available:
// arrow-fn: named arrow function export
arrow-fn name=formatArticle params="a: Article" returnType=": string"
handler <<<
return `[${a.status}] ${a.title}`;
>>>
// window-event: browser event hook with cleanup
window-event hookName=useResize eventName="'resize'"
handler <<<
setWidth(window.innerWidth);
>>>Self-extending: kern evolve
Don't have a template for your library? KERN can learn it from your codebase:
kern evolve src/ --recursive # Detect patterns -> propose templates
kern evolve:review --list # Review proposals with split-view
kern evolve:review --approve <id> # Approve a proposal
kern evolve:review --promote --local # Write to templates/Evolve scans your TypeScript, finds patterns from library families (react-hook-form, redux-toolkit, framer-motion, axios, yup, valibot, and more), and generates .kern template proposals with typed slots.
Each proposal is validated through a 5-step pipeline: parse, register, expand, golden-diff, typecheck.
Template expansion depth
Templates can reference other templates (nested expansion). The compiler enforces a maximum expansion depth of 10 to prevent infinite recursion.