packages/component/skills/create-mixins/SKILL.md
@remix-run/component)Use this skill when authoring new mixins in packages/component.
The key principle: model the real runtime contract first, then write the smallest code that matches it.
Treat these as constraints, not suggestions:
insert is the host-node availability point for imperative setup.remove is teardown for that same lifecycle.queueTask runs post-commit and receives (node, signal) for mixins.insert, remove, or queued work.createMixin<NodeType>((handle) => {
// Setup runs once per handle lifecycle.
handle.addEventListener('insert', (event) => {
// event.node is the mounted host node for this lifecycle.
// Attach imperative effects here.
})
handle.addEventListener('remove', () => {
// Teardown for the same lifecycle.
// Remove listeners, abort work, release resources.
})
return (props) => {
// Render stays pure: derive props/JSX only.
// Post-commit work goes in queueTask when needed.
handle.queueTask((node) => {
// Runs after commit with the concrete host node.
})
return <handle.element {...props} />
}
})
If your implementation assumes semantics that do not exist (node swapping, repeated insert for the same handle, etc.), remove that logic.
insert for attach/setup.remove for detach/cleanup.invariant(...) when a condition is guaranteed by runtime and violation means framework bug.queueTask((node, signal) => ...) for post-commit DOM work.
node is needed.signal only when work is async or cancellation-sensitive.signal.aborted checks for purely synchronous work.let withTitle = createMixin((handle) => (title: string, props: { title?: string }) => (
<handle.element {...props} title={title} />
))
let withFocus = createMixin<HTMLElement>((handle) => {
handle.addEventListener('insert', (event) => {
event.node.focus()
})
return (props) => <handle.element {...props} />
})
handle.queueTask((node) => {
node.removeEventListener(prevType, stableHandler, prevCapture)
node.addEventListener(nextType, stableHandler, nextCapture)
})
signal.aborted in synchronous non-racy code as boilerplate.insert/remove directly.queueTask used only where post-commit timing is required.createMixin<ThisType> is preserved.