Skip to content

Instantly share code, notes, and snippets.

@sillvva
Last active November 26, 2025 15:04
Show Gist options
  • Select an option

  • Save sillvva/3079c396d0e22dfdfa30a4bfdbd4734c to your computer and use it in GitHub Desktop.

Select an option

Save sillvva/3079c396d0e22dfdfa30a4bfdbd4734c to your computer and use it in GitHub Desktop.
Bindable input
<script lang="ts" generics="V extends RemoteFormFieldValue">
import type { RemoteFormField, RemoteFormFieldValue } from "@sveltejs/kit";
import type { HTMLInputAttributes } from "svelte/elements";
import FieldMessage from "./field-message.svelte";
import InputWrapper from "./input-wrapper.svelte";
type InputTypeMap =
| {
type?: "text";
field: RemoteFormField<string>;
value?: undefined;
}
| {
type: "email" | "password" | "url" | "tel" | "search" | "button" | "reset" | "color" | "button";
field: RemoteFormField<string>;
value?: undefined;
}
| { type: "number" | "range"; field: RemoteFormField<number>; value?: undefined }
| {
type: "checkbox";
value?: undefined;
field: RemoteFormField<boolean>;
}
| {
type: "checkbox";
value: string;
field: RemoteFormField<string[]>;
}
| {
type: "radio";
value: string;
field: RemoteFormField<string>;
}
| {
type: "hidden";
value?: string;
field: RemoteFormField<string>;
}
| {
type: "submit";
value: string;
field: RemoteFormField<string>;
}
| {
type: "file";
field: RemoteFormField<File>;
value?: undefined;
}
| {
type: "file multiple";
field: RemoteFormField<File[]>;
value?: undefined;
};
type Props = {
label?: string;
field: RemoteFormField<V>;
description?: string;
warning?: string;
hidden?: boolean;
} & InputTypeMap &
Omit<HTMLInputAttributes, "type" | "name" | "id" | "value" | "checked" | "defaultValue" | "defaultChecked">;
let { label, description, warning, hidden, required, type, value = $bindable(), field, ...rest }: Props = $props();
const attributes = $derived.by(() => {
if (type === "hidden" || type === "submit" || type === "radio") {
const f = field as RemoteFormField<string>;
return f.as(type, value || (field.value() as string) || "");
} else if (type === "checkbox" && typeof value === "string") {
const f = field as RemoteFormField<string[]>;
return f.as("checkbox", value);
} else if (type === "number" || type === "range") {
const f = field as RemoteFormField<number>;
return f.as(type);
} else if (type === "file") {
const f = field as RemoteFormField<File>;
return f.as(type);
} else if (type === "file multiple") {
const f = field as RemoteFormField<File[]>;
return f.as(type);
} else {
const t = type as "text" | "email" | "password" | "url" | "tel" | "search" | "button" | "reset" | "color";
const f = field as RemoteFormField<string>;
return f.as(t);
}
});
const withRest = $derived({ ...rest, ...attributes });
const name = $derived(attributes.name);
const issues = $derived(field.issues());
const invalid = $derived(!!issues?.length || undefined);
</script>
{#if type === "checkbox"}
<label
class={[
"label flex cursor-pointer rounded-lg border p-4 text-sm",
invalid ? "border-error" : "border-base-content/20",
hidden && "hidden"
]}
>
<div class="flex flex-1 flex-col gap-0.5">
<span>
<span class="text-base-content">{label}</span>
{#if required}
<span class="text-error">*</span>
{/if}
</span>
<FieldMessage {name} type="checkbox" {description} {warning} {issues} />
</div>
<input {...withRest} bind:value aria-invalid={invalid} id={name} type="checkbox" class="checkbox-primary checkbox" />
</label>
{:else}
<InputWrapper type={hidden ? "hidden" : type || "text"} {label} {required}>
<input
{...withRest}
bind:value
aria-invalid={invalid}
id={name}
class={[type !== "hidden" && !hidden && "input focus:border-primary focus:aria-[invalid]:border-error w-full"]}
{hidden}
/>
</InputWrapper>
{/if}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment