6.9k

F

Previous Next

Form field wrappers with built-in labels, descriptions, and error handling. Less boilerplate than field components.

About

Field components exist but they're very verbose. So there is now "f" which is a shorter version of the field components that should integrate better into laravel. These are opinionated form wrappers that combine labels, inputs, descriptions, and error messages into single components. If you don't want to write Field.Field and Field.Label and Field.Error every time, these might work better for you.

Each component handles the whole field structure internally, so you just bind a value and pass in label/error props. That's it.

Installation

pnpm dlx shadcn-svelte@latest add f

Usage

<script lang="ts">
  import { FInput, FSelect, FDate } from "$lib/components/ui/f";
 
  let name = $state("");
  let email = $state("");
  let errors = $state({ name: null, email: null });
</script>
<form>
  <FInput
    bind:value={name}
    label="Name"
    error={errors.name}
    description="Your full name"
    required
  />
 
  <FInput bind:value={email} label="Email" error={errors.email} type="email" />
</form>

Components

The package includes wrappers for most common form inputs:

  • FInput - Text inputs
  • FTextarea - Multi-line text
  • FSelect - Dropdown select with badges
  • FRadio - Radio button groups
  • FDate - Date picker with calendar
  • FDateTime - Date and time picker
  • FDateRange - Date range picker with presets
  • FTime - Time input
  • FFileUpload - File upload with FilePond
  • FSignature - Signature pad
  • FDisplay - Read-only display of values
  • FRelatedSingle - Dialog-based item selector

There are also low-level components if you need them: Input, Checkbox, Description.

Examples

Text Input

Basic text input with label, description, and error support.

<script lang="ts">
  let username = $state("");
  let error = $state(null);
</script>
 
<FInput
  bind:value={username}
  label="Username"
  description="Choose a unique username"
  {error}
  placeholder="evilrabbit"
  required
/>

Common props for all components:

  • label - Field label
  • description - Help text below the input
  • error - Error message (shows if not null)
  • required - Shows asterisk next to label

Textarea

Multi-line text input.

<FTextarea
  bind:value={bio}
  label="Bio"
  description="Tell us about yourself"
  rows={4}
  placeholder="I'm a developer..."
/>

Select

Dropdown with support for single/multiple selection and badge display.

<script lang="ts">
  let role = $state("user");
 
  const options = [
    { value: "admin", label: "Administrator" },
    { value: "user", label: "User" },
    { value: "guest", label: "Guest" },
  ];
</script>
 
<FSelect
  bind:value={role}
  label="Role"
  {options}
  type="single"
  placeholder="Select a role"
/>

For multiple selection:

<FSelect
  bind:value={tags}
  label="Tags"
  {options}
  type="multiple"
  maxDisplayItems={3}
/>

The maxDisplayItems prop controls how many badges show before it displays "+N more".

Radio Group

Radio buttons with optional additional text input.

<script lang="ts">
  let choice = $state({ selectedValue: null, additionalText: null });
 
  const options = [
    { value: "yes", label: "Yes" },
    { value: "no", label: "No" },
    { value: "other", label: "Other", additional_text_input: true },
  ];
</script>
 
<FRadio
  bind:value={choice}
  label="Do you agree?"
  {options}
  name="agreement"
  required
/>

If an option has additional_text_input: true, it shows a text field when selected.

Date Picker

Calendar-based date picker.

<script lang="ts">
  import { CalendarDate } from "@internationalized/date";
 
  let birthdate = $state(null);
  let min = new CalendarDate(1900, 1, 1);
  let max = new CalendarDate(2010, 12, 31);
</script>
 
<FDate
  bind:value={birthdate}
  label="Birth Date"
  {min}
  {max}
  description="Must be 18 or older"
/>

DateTime Picker

Date and time picker with optional presets.

<script lang="ts">
  import { dateTimePresets } from "$lib/components/ui/f/dateTime";
 
  let appointment = $state(null);
</script>
 
<FDateTime
  bind:value={appointment}
  label="Appointment"
  presets={dateTimePresets}
/>

The presets give quick options like "Now", "In 1 hour", "Tomorrow at 9am", etc.

Date Range

Pick a start and end date with preset ranges.

<script lang="ts">
  import { dateRangePresets } from "$lib/components/ui/f/dateRange";
 
  let range = $state(null);
</script>
 
<FDateRange bind:value={range} label="Date Range" presets={dateRangePresets} />

Presets include "Today", "Last 7 Days", "Last 30 Days", etc.

Time Input

Time picker that returns an object with hour, min, and sec.

<script lang="ts">
  let meetingTime = $state({ hour: 9, min: 0, sec: 0 });
</script>
 
<FTime bind:value={meetingTime} label="Meeting Time" />

You can use the exported strToTime and timeToStr helper functions for conversions:

<script lang="ts">
  import { strToTime, timeToStr } from "$lib/components/ui/f";
 
  const time = strToTime("14:30:00"); // { hour: 14, min: 30, sec: 0 }
  const str = timeToStr(time); // "14:30:00"
</script>

File Upload

FilePond-based file uploader with preview support.

<script lang="ts">
  let fileIds = $state(new Set());
 
  const handleProcess = (error, file) => {
    if (!error && file.serverId) {
      fileIds.add(file.serverId);
    }
  };
 
  const handleRemove = (error, file) => {
    if (!error && file.serverId) {
      fileIds.delete(file.serverId);
    }
  };
</script>
 
<FFileUpload
  label="Profile Picture"
  acceptedFileTypes={["image/*"]}
  allowMultiple={false}
  serverIds={fileIds}
  onProcessFile={handleProcess}
  onRemoveFile={handleRemove}
/>

You'll need to set up a FilePond server endpoint to handle the actual uploads.

Signature Pad

Canvas-based signature capture.

<script lang="ts">
  let signature = $state(null);
</script>
 
<FSignature
  bind:value={signature}
  label="Signature"
  name="signature"
  description="Sign above"
/>

The value includes both the point groups (for replay) and SVG output.

Display

Read-only display of values. Good for confirmation pages or readonly forms.

<FDisplay label="Email" value={user.email} />
 
<FDisplay label="Roles" value={["Admin", "User", "Editor"]} />

Arrays are displayed as lists. You can also pass a snippet for custom rendering:

<FDisplay label="Status">
  {#snippet children()}
    <Badge variant={user.active ? "default" : "destructive"}>
      {user.active ? "Active" : "Inactive"}
    </Badge>
  {/snippet}
</FDisplay>

Dialog-based selector for picking related items. You provide the content for the dialog.

<script lang="ts">
  let selectedUserId = $state(null);
  let selectedUserName = $state(null);
  let isOpen = $state(false);
 
  const selectUser = (user) => {
    selectedUserId = user.id;
    selectedUserName = user.name;
    isOpen = false;
  };
</script>
 
<FRelatedSingle
  bind:value={selectedUserId}
  bind:valLabel={selectedUserName}
  bind:isOpen
  label="Assigned To"
  required
>
  {#snippet children()}
    <Table>
      {#each users as user}
        <TableRow onclick={() => selectUser(user)}>
          <TableCell>{user.name}</TableCell>
        </TableRow>
      {/each}
    </Table>
  {/snippet}
</FRelatedSingle>

The component shows a button that opens a dialog. Put whatever you want in the dialog content to let users pick something.

Validation

All components support an error prop. Just pass in your error message and it displays below the input:

<script lang="ts">
  let email = $state("");
  let errors = $state({});
 
  const validate = () => {
    errors = {};
    if (!email.includes("@")) {
      errors.email = "Enter a valid email address";
    }
  };
</script>
 
<FInput bind:value={email} label="Email" error={errors.email} type="email" />

The components don't do validation themselves - that's your job. They just display whatever error you give them.

Working with Forms

These work fine with SvelteKit form actions or client-side validation:

<script lang="ts">
  import { enhance } from "$app/forms";
 
  let form = $state({ name: "", email: "" });
  let errors = $state({});
</script>
 
<form method="POST" use:enhance>
  <FInput
    bind:value={form.name}
    name="name"
    label="Name"
    error={errors.name}
    required
  />
 
  <FInput
    bind:value={form.email}
    name="email"
    label="Email"
    error={errors.email}
    type="email"
    required
  />
 
  <button type="submit">Submit</button>
</form>

Or with something like Superforms:

<script lang="ts">
  import { superForm } from "sveltekit-superforms";
 
  const { form, errors } = superForm(data.form);
</script>
 
<FInput
  bind:value={$form.username}
  label="Username"
  error={$errors.username?.[0]}
/>

Props Reference

Common props across most components:

  • label?: string - Field label text
  • description?: string - Help text
  • error?: string | null - Error message
  • required?: boolean - Show required asterisk
  • disabled?: boolean - Disable the input
  • class?: string - Additional CSS classes
  • ref?: HTMLElement - Element reference

Component-specific props are pretty straightforward - check the TypeScript definitions or just look at the component source if you need details.

Notes

  • These components use $bindable() for two-way binding
  • They're opinionated and might not fit every use case
  • If you need more control, use the regular Field components
  • The date/time components need @internationalized/date installed
  • File upload needs svelte-filepond and plugins
  • Signature needs signature_pad
  • NLP date needs the nlp-date-input component

That's about it. They're just wrappers to save you some typing.