Craftit API Reference
Package Entry Points
@vielzeug/craftit— core API (stable)@vielzeug/craftit/labs— experimental APIs@vielzeug/craftit/directives— template directives@vielzeug/craftit/test— testing helpers
API At a Glance
| Symbol | Purpose | Execution mode | Common gotcha |
|---|---|---|---|
defineComponent() | Define and register a custom element | Sync | Element names must include a hyphen |
html | Create reactive templates | Sync | Do not build templates with string concatenation |
onMount() | Run setup logic tied to component lifecycle | Sync | Always return cleanup when registering listeners |
Core Component API
defineComponent(options)
defineComponent<Props, Events>(options: DefineComponentOptions<BuildPropSchema<Props>, Events>): string;Registers a custom element and returns the tag name.
If the tag already exists, Craftit throws [craftit:E9].
DefineComponentOptions:tag: stringsetup(ctx): string | HTMLResultprops?: Record<string, PropDef<any>>styles?: (string | CSSStyleSheet | CSSResult)[]host?: Record<string, string | boolean | number>formAssociated?: booleanshadow?: Omit<ShadowRootInit, 'mode'>
DefineComponentSetupContext:host: HTMLElementshadow: ShadowRootprops: InferPropsSignals<...>emit: EmitFn<Events>slots: Slotsreflect(config)
Runtime Helpers
Lifecycle
onMount(fn)— run logic after mount.onCleanup(fn)— register cleanup.onError(fn)— component-scoped error handler.
onCleanup() is component-aware: inside component setup/mount it runs on unmount; outside component context it delegates to stateit's effect cleanup behavior.
Reactivity Wrappers
effect(fn, options?)— component-aware wrapper around stateiteffect.watch(source, cb, options?)— component-aware watcher.
Signatures:
watch<T>(source: ReadonlySignal<T>, cb: (value: T, prev: T) => void, options?: WatchOptions<T>): Subscriptionwatch(sources: ReadonlyArray<ReadonlySignal<unknown>>, cb: () => void, options?: WatchOptions<unknown>): Subscription
DOM/Event Utilities
handle(target, event, listener, options?)— listener with auto-cleanup.fire.basic(target, type, options?)— dispatches a plainEvent.fire.custom(target, type, options?)— dispatches aCustomEvent.fire.mouse(target, type, options?)— dispatches aMouseEvent.fire.keyboard(target, type, options?)— dispatches aKeyboardEvent.fire.focus(target, type, options?)— dispatches aFocusEvent.fire.touch(target, type, options?)— dispatches aTouchEventwhen available, otherwiseCustomEvent.fire.event(target, event)— dispatches a prebuilt event instance.aria(attrs)oraria(target, attrs)— reactive ARIA attributes (false,null, andundefinedremove the attribute).
Template and Styling
html
html(strings: TemplateStringsArray, ...values: unknown[]): HTMLResult;Tagged template with reactive bindings.
Template event bindings support modifiers:
@click.stop=${handler}@submit.prevent=${handler}@click.self=${handler}@keydown.once=${handler}- listener options:
.capture,.passive,.once
css
css(strings: TemplateStringsArray, ...values: unknown[]): CSSResult;Returns { content: string; toString(): string }.
Props API
prop(name, defaultValue, options?)
prop<T>(name: string, defaultValue: T, options?: PropOptions<T>): Signal<T>;PropOptions<T>:
parse?: (value: string | null) => Treflect?: booleanomit?: booleantype?: StringConstructor | NumberConstructor | BooleanConstructor | ArrayConstructor | ObjectConstructor
typed(defaultValue, options?)
Helper for explicit generic prop typing in defineComponent({ props }).
Slots and Emits
setup-context emit
setup({ emit }) receives a typed emit function.
type EmitFn<T extends Record<string, unknown>> = {
<K extends KeysWithoutDetail<T>>(event: K): void;
<K extends Exclude<keyof T, KeysWithoutDetail<T>>>(event: K, detail: T[K]): void;
};
type KeysWithoutDetail<T extends Record<string, unknown>> = {
[P in keyof T]: [T[P]] extends [void | undefined | never] ? P : never;
}[keyof T];Example:
type Events = { open: void; select: { value: string } };
emit('open');
emit('select', { value: 'alpha' });setup-context slots
setup({ slots }) receives a slots helper:
slots.has(name): ReadonlySignal<boolean>If Craftit cannot find a matching <slot> element for name, it warns once in dev tooling and returns a signal that stays false.
onSlotChange(slotName, callback)
Listens for slot assignment changes (call inside onMount).
If no matching <slot> exists, Craftit warns once instead of failing silently.
Context API
createContext<T>(description?)provide(key, value)inject(key)/inject(key, fallback)injectOptional(key)injectRequired(key)syncContextProps(ctx, props, keys)
Types:
InjectionKey<T>
Form-Associated API
defineField(options, callbacks?)
defineField<T>(options: FormFieldOptions<T>, callbacks?: FormFieldCallbacks): FormFieldHandle;Requires defineComponent({ formAssociated: true, ... }); otherwise Craftit throws an explicit runtime error.
Types:
FormFieldOptions<T>value: Signal<T> | ReadonlySignal<T>toFormValue?: (value: T) => string | File | FormData | nulldisabled?: Signal<boolean> | ReadonlySignal<boolean> | ComputedSignal<boolean>
FormFieldCallbacksonAssociated?,onDisabled?,onReset?,onStateRestore?
FormFieldHandleinternals,setValidity,setCustomValidity,checkValidity,reportValidity
Labs APIs
Import from @vielzeug/craftit/labs:
createListNavigation(options)- returns result-based navigation (
ListNavigationResultwithreason,moved,wrapped,index)
- returns result-based navigation (
createOverlayControl(options)- reason-aware overlay transitions via
setOpen(next, { reason }),onOpen(reason), andonClose(reason)
- reason-aware overlay transitions via
createSelectionControl(options)- key-driven selection via
keyExtractorandfindByKey
- key-driven selection via
useA11yControl(host, config)- explicit helper tone (
'default' | 'error'), no text heuristics
- explicit helper tone (
createCheckableControl(config)observeResize(el): ReadonlySignal<{ width: number; height: number }>observeIntersection(el, options?): ReadonlySignal<IntersectionObserverEntry | null>observeMedia(query): ReadonlySignal<boolean>
Labs contract notes
aria(...)semantics apply broadly:false,null, andundefinedremove ARIA attributes.createOverlayControlclose/open reasons are part of the public contract and intended for typed event payloads.createSelectionControlintentionally avoids item-shape assumptions; keys are the stable API surface.
Main API (@vielzeug/craftit) also exports:
observeResize(el): ReadonlySignal<{ width: number; height: number }>
Utility APIs
createId(prefix?)createFormIds(prefix, name?)guard(condition, handler)escapeHtml(value)toKebab(str)
Also exported:
ref,refs- internal types including
HTMLResult,Directive,Ref,Refs,RefCallback
Directive APIs
each(source, template, empty?, options?)
- Static array source:
options.keyoptional. - Reactive source (
Signal<T[]>or() => T[]):options.keyrequired. options.selectfilters items before rendering.
For dynamic lists with interactions, prefer event delegation on a stable parent element.
Exports:
attrbindchooseclasseseachmatchmemoonrawspreadstyleuntilwhen
Common signatures:
when(condition, thenFn, elseFn?)
match(...branchesOrFallback)
until(promise, pendingFn?, onError?)
each(source, template, empty?, options?)
bind(sig)until(...) renders Error: <reason> by default when the promise rejects and onError is omitted.
attr(map)— shorthand for batching DOM property bindings in spread position. Despite the name, entries map to.propertybindings internally.bind(sig)— two-way shorthand built on the same property-binding path used byattr(...),spread({ '.value': ... }), and template.value/.checkedbindings.
Testing APIs
Primary exports:
mount(...)flush()within(element)fire(event helpers)user(interaction helpers)waitFor(...)waitForEvent(...)mock(tagName, template?)cleanup()install(afterEachHook)
Core test types:
Fixture<T extends HTMLElement>MountOptionsQueryScopeWaitOptions
mount(...) accepts:
- a registered tag name
- an inline
setup(ctx) => templatefunction - an inline
defineComponent-style options object withouttag
Stateit Re-Exports
Craftit re-exports @vielzeug/stateit from its main entrypoint. Use Craftit imports directly when building components, or import from @vielzeug/stateit for state-only modules.
import { signal, computed, batch, untrack, readonly } from '@vielzeug/craftit';