Variant props
Boolean props such as <Button small> read nicely in templates. Inside the component you usually want one value. This chapter shows how to type the props and then collapse them.
flowchart LR
A["Boolean props: small, compact"] --> B["XOR / AtLeastOne: restrict combinations"]
B --> C["variantMap: one string value"]
XOR
XOR enforces mutually exclusive prop shapes. It takes two to eight type arguments. Include {} as an argument to allow no variant.
type Props = XOR<{ small: true }, { compact: true }, {}>;
type ButtonProps = XOR<{ primary: true }, { secondary: true }, { ghost: true }, {}>;
Without the {} argument, one of the variants is required.
AtLeastOne
Requires at least one property of an object type, while allowing several.
type LabelOrIcon = AtLeastOne<{ label: string; icon: IconDefinition }>;
Use XOR for "exactly one (or none)", AtLeastOne for "one or more".
variantMap
variantMap takes an object of booleans and a default, and returns the key of the first truthy entry (in object iteration order), or the default.
variantMap({ small: false, compact: true }, "normal");
variantMap({ small: false, compact: false }, "normal");
variantMap({ small: true, compact: true }, "normal");
The return type is the union of the keys and the default, for example "small" | "compact" | "normal".
Reactivity
Inside a component, props change over time. Choose deliberately:
import { untrack } from "svelte";
const size = untrack(() => variantMap({ small, compact }, "normal"));
const size = $derived(variantMap({ small, compact }, "normal"));
Putting it together
<script lang="ts">
import { variantMap } from "@atom-forge/svelte-helpers";
import type { XOR } from "@atom-forge/svelte-helpers";
type Props = XOR<{ small: true }, { compact: true }, {}>;
let { small, compact }: Props = $props();
const size = $derived(variantMap({ small, compact }, "normal"));
</script>
Variant props
Boolean props such as
<Button small>read nicely in templates. Inside the component you usually want one value. This chapter shows how to type the props and then collapse them.XOR
XORenforces mutually exclusive prop shapes. It takes two to eight type arguments. Include{}as an argument to allow no variant.// small OR compact, or neither, but not both type Props = XOR<{ small: true }, { compact: true }, {}>; type ButtonProps = XOR<{ primary: true }, { secondary: true }, { ghost: true }, {}>;Without the
{}argument, one of the variants is required.AtLeastOne
Requires at least one property of an object type, while allowing several.
type LabelOrIcon = AtLeastOne<{ label: string; icon: IconDefinition }>;Use
XORfor "exactly one (or none)",AtLeastOnefor "one or more".variantMap
variantMaptakes an object of booleans and a default, and returns the key of the first truthy entry (in object iteration order), or the default.variantMap({ small: false, compact: true }, "normal"); // "compact" variantMap({ small: false, compact: false }, "normal"); // "normal" variantMap({ small: true, compact: true }, "normal"); // "small"The return type is the union of the keys and the default, for example
"small" | "compact" | "normal".Reactivity
Inside a component, props change over time. Choose deliberately:
import { untrack } from "svelte"; // Captures the initial selection only const size = untrack(() => variantMap({ small, compact }, "normal")); // Updates when the props change const size = $derived(variantMap({ small, compact }, "normal"));Putting it together
<script lang="ts"> import { variantMap } from "@atom-forge/svelte-helpers"; import type { XOR } from "@atom-forge/svelte-helpers"; type Props = XOR<{ small: true }, { compact: true }, {}>; let { small, compact }: Props = $props(); const size = $derived(variantMap({ small, compact }, "normal")); </script>