pacekit
componentform-switchsource: ok · hit
Form Switch
A switch input field with on/off state and validation.
npx shadcn add "https://ui.pacekit.dev/r/form-switch.json"free
Where it goes
stats
Interaction
animated-counter
Tags auto-generated by rules on 9/24/2026 — empty means no evidence, not lack of support.
Dependencies
Bundle weight · light@tanstack/react-form
Registry dependencies
fieldswitch
Files3 · source cached 10/14/2026, 2:17:13 AM
components/ui/forms/form-switch.tsx
import { type AnyFormApi, type DeepKeys, useField } from "@tanstack/react-form";
import type { ComponentProps, ReactNode } from "react";
import { cn } from "@/lib/utils";
import { Field, FieldLabel } from "@/components/ui/field";
import { FormError } from "@/components/ui/forms/form-error";
import { type GetFormData } from "@/components/ui/forms/helpers";
import { Switch } from "@/components/ui/switch";
export type FormSwitchProps<TForm extends AnyFormApi> = Omit<ComponentProps<typeof Switch>, "name" | "form"> & {
label?: ReactNode;
form: TForm;
name: DeepKeys<GetFormData<TForm>>;
containerClassName?: string;
};
export const FormSwitch = <TForm extends AnyFormApi>({
label,
form,
name,
className,
containerClassName,
...rest
}: FormSwitchProps<TForm>) => {
const field = useField({
form,
name,
});
const errors = field.state.meta.errors[0];
const error = typeof errors === "object" ? errors.message : errors;
return (
<Field data-invalid={error != undefined} className={cn("w-fit gap-1.5", containerClassName)}>
<div className="flex cursor-pointer items-center gap-3">
<Switch
id={field.name}
name={field.name}
checked={field.state.value as boolean}
onCheckedChange={field.handleChange}
onBlur={field.handleBlur}
aria-invalid={error != undefined}
className={className}
{...rest}
/>
{label && (
<FieldLabel className="cursor-pointer" htmlFor={field.name}>
{label}
</FieldLabel>
)}
</div>
<FormError form={form} name={name} />
</Field>
);
};
components/ui/forms/form-error.tsx
import { type AnyFormApi, type DeepKeys, useField } from "@tanstack/react-form";
import type { InputHTMLAttributes } from "react";
import { cn } from "@/lib/utils";
import { type GetFormData } from "@/components/ui/forms/helpers";
export type FormErrorProps<TForm extends AnyFormApi> = Omit<
InputHTMLAttributes<HTMLParagraphElement>,
"name" | "form"
> & {
form: TForm;
name: DeepKeys<GetFormData<TForm>>;
};
export const FormError = <TForm extends AnyFormApi>({ form, name = "", className, ...rest }: FormErrorProps<TForm>) => {
const field = useField({
form,
name,
});
const errors = field.state.meta.errors[0];
const error = typeof errors === "object" ? errors.message : errors;
return (
<>
{error && (
<p {...rest} className={cn("text-destructive text-sm", className)}>
{error}
</p>
)}
</>
);
};
components/ui/forms/helpers.ts
import { type AnyFormApi, type DeepKeys, type FormApi } from "@tanstack/react-form";
export type GetFormData<TForm> =
TForm extends FormApi<infer TData, any, any, any, any, any, any, any, any, any, any, any> ? TData : never;
export const setFieldError = <TForm extends AnyFormApi>(
form: TForm,
field: DeepKeys<GetFormData<TForm>>,
error: string,
) => {
form.setFieldMeta(field, (prev) => ({
...prev,
errorMap: {
...prev.errorMap,
onSubmit: error,
},
}));
};
export const clearFormErrors = <TForm extends AnyFormApi>(form: TForm) => {
form.setErrorMap({ onDynamic: { fields: {} } });
};