Guides: Directives and transitions

Guides

Directives and transitions

Sometimes a component needs the element itself: to focus it, measure it, hand it to a charting library, or listen for something JSX has no attribute for. A directive is a function that receives the element when it's created.

A directive

src/lib/autofocus.ts
export function autofocus(node: Element) {
	(node as HTMLElement).focus();
}
src/routes/search.tsx
import { autofocus } from "../lib/autofocus";

const Search = () => <input type="search" name="q" use={autofocus} />;

export default Search;

use={fn} calls fn(element) once, when the element is created. On a server-rendered page that's when the page hydrates; on the server itself directives don't run.

Parameters and cleanup

Pass a tuple to give the directive a second argument, and return a function to run cleanup when the element goes away:

src/lib/clickOutside.ts
export function clickOutside(node: Element, onOutside: () => void) {
	const listener = (e: MouseEvent) => {
		if (!node.contains(e.target as Node)) onOutside();
	};
	document.addEventListener("click", listener, true);
	return () => document.removeEventListener("click", listener, true);
}
src/lib/Menu.tsx
import { clickOutside } from "./clickOutside";

export const Menu = () => {
	let open = $state(false);
	return (
		<div class="menu" use={[clickOutside, () => (open = false)]}>
			<button onClick={() => (open = !open)}>Menu</button>
			{open && (
				<ul>
					<li>
						<a href="/settings">Settings</a>
					</li>
				</ul>
			)}
		</div>
	);
};

The cleanup runs when the component that rendered the element is torn down, for example when the router navigates to another page.

enhance from @sigil-dev/grimoire/client is a directive like these (Form actions).

Transitions

transition:, in: and out: animate an element as it appears and disappears. The part after the colon is a label for readers; the value is a function that receives the element and returns a Web Animation:

src/lib/fade.ts
export function fade(el: Element, ms: unknown, direction: "in" | "out") {
	return el.animate([{ opacity: 0 }, { opacity: 1 }], {
		duration: typeof ms === "number" ? ms : 200,
		direction: direction === "in" ? "normal" : "reverse",
		easing: "ease-out",
	});
}
src/lib/Notice.tsx
import { fade } from "./fade";

export const Notice = () => {
	let shown = $state(true);
	return (
		<>
			<button onClick={() => (shown = !shown)}>Toggle</button>
			{shown && <p transition:fade={[fade, 300]}>Saved.</p>}
		</>
	);
};
  • in: plays when the element is created.
  • out: plays when the element is removed, and the element stays in the page until the animation finishes.
  • transition: does both.

Pass [fn, params] to hand the function a second argument, as with use.

@sigil-dev/runtime exports easing functions (easeOutCubic, easeInOutQuad, and so on) for animations you drive yourself (Runtime).

Edit this page