Docs
Forms

Forms

Common form patterns used in Studio settings pages and side panels.

Forms in Supabase Studio should follow consistent patterns to ensure a cohesive user experience across settings pages and side panels. This guide covers the most common form patterns and field types.

Page Layout

Forms in page layouts typically use PageSection components with Card containers. Fields use FormItemLayout with layout="flex-row-reverse" for horizontal alignment.

Loading...
import { zodResolver } from '@hookform/resolvers/zod'
import { format } from 'date-fns'
import { CalendarIcon, ExternalLink, Plus, Trash2, Upload } from 'lucide-react'
import { useRef, useState } from 'react'
import { useFieldArray, useForm } from 'react-hook-form'
import * as z from 'zod'
 
import {
  Button,
  Calendar,
  Card,
  CardContent,
  CardFooter,
  Checkbox_Shadcn_,
  FormControl_Shadcn_,
  FormField_Shadcn_,
  Form_Shadcn_,
  Input_Shadcn_,
  PrePostTab,
  Popover_Shadcn_,
  PopoverContent_Shadcn_,
  PopoverTrigger_Shadcn_,
  RadioGroupStacked,
  RadioGroupStackedItem,
  Select_Shadcn_,
  SelectContent_Shadcn_,
  SelectItem_Shadcn_,
  SelectTrigger_Shadcn_,
  SelectValue_Shadcn_,
  Separator,
  Switch,
  Textarea,
} from 'ui'
import { FormItemLayout } from 'ui-patterns/form/FormItemLayout/FormItemLayout'
import { Input } from 'ui-patterns/DataInputs/Input'
import {
  MultiSelector,
  MultiSelectorContent,
  MultiSelectorItem,
  MultiSelectorList,
  MultiSelectorTrigger,
} from 'ui-patterns/multi-select'
import {
  PageSection,
  PageSectionContent,
  PageSectionMeta,
  PageSectionSummary,
  PageSectionTitle,
} from 'ui-patterns/PageSection'
 
const formSchema = z.object({
  name: z.string().min(1, 'Name is required'),
  description: z.string().optional(),
  maxConnections: z.number().min(1).max(1000),
  enableFeature: z.boolean(),
  enableRls: z.boolean(),
  enableNotifications: z.boolean(),
  enableAnalytics: z.boolean(),
  region: z.string().min(1, 'Region is required'),
  schemas: z.array(z.string()).min(1, 'At least one schema is required'),
  queueType: z.enum(['basic', 'partitioned']),
  expiryDate: z.date().optional(),
  password: z.string().min(8, 'Password must be at least 8 characters'),
  duration: z.number().min(5).max(30),
  redirectUris: z.array(z.object({ value: z.string().url('Must be a valid URL') })),
  apiKey: z.string().optional(),
})
 
const fakeApiKey = 'sk_live_51H3x4mpl3_4nd_53cur3_k3y_1234567890'
 
export function FormPatternsPageLayout() {
  const uploadButtonRef = useRef<HTMLInputElement>(null)
  const fileUploadRef = useRef<HTMLInputElement>(null)
  const [logoFile, setLogoFile] = useState<File>()
  const [logoUrl, setLogoUrl] = useState<string>()
  const [uploadedFiles, setUploadedFiles] = useState<File[]>([])
  const [isDragging, setIsDragging] = useState(false)
 
  const form = useForm<z.infer<typeof formSchema>>({
    resolver: zodResolver(formSchema),
    defaultValues: {
      name: '',
      description: '',
      maxConnections: 10,
      enableFeature: false,
      enableRls: true,
      enableNotifications: false,
      enableAnalytics: true,
      region: '',
      schemas: ['public'],
      queueType: 'basic',
      expiryDate: undefined,
      password: '',
      duration: 10,
      redirectUris: [{ value: '' }],
      apiKey: fakeApiKey,
    },
  })
 
  const { fields, append, remove } = useFieldArray({
    control: form.control,
    name: 'redirectUris',
  })
 
  function onSubmit(values: z.infer<typeof formSchema>) {
    console.log(values)
  }
 
  return (
    <div className="w-full">
      <PageSection className="py-0">
        <PageSectionMeta>
          <PageSectionSummary>
            <PageSectionTitle>Form Settings</PageSectionTitle>
          </PageSectionSummary>
        </PageSectionMeta>
        <PageSectionContent>
          <Form_Shadcn_ {...form}>
            <form onSubmit={form.handleSubmit(onSubmit)} className="space-y-4">
              <Card>
                <CardContent className="pt-6 space-y-6">
                  {/* Text Input */}
                  <FormField_Shadcn_
                    control={form.control}
                    name="name"
                    render={({ field }) => (
                      <FormItemLayout
                        layout="flex-row-reverse"
                        label="Text Input"
                        description="Single-line text entry for short values"
                        className="[&>div]:md:w-1/2"
                      >
                        <FormControl_Shadcn_>
                          <Input_Shadcn_ {...field} placeholder="Enter text" />
                        </FormControl_Shadcn_>
                      </FormItemLayout>
                    )}
                  />
 
                  <Separator className="bg-border -mx-6 w-[calc(100%+3rem)]" />
 
                  {/* Password Input */}
                  <FormField_Shadcn_
                    control={form.control}
                    name="password"
                    render={({ field }) => (
                      <FormItemLayout
                        layout="flex-row-reverse"
                        label="Password Input"
                        description="Masked input for secure text entry"
                        className="[&>div]:md:w-1/2"
                      >
                        <FormControl_Shadcn_>
                          <Input_Shadcn_ {...field} type="password" placeholder="Enter password" />
                        </FormControl_Shadcn_>
                      </FormItemLayout>
                    )}
                  />
 
                  <Separator className="bg-border -mx-6 w-[calc(100%+3rem)]" />
 
                  {/* Copyable Input */}
                  <FormField_Shadcn_
                    control={form.control}
                    name="apiKey"
                    render={() => (
                      <FormItemLayout
                        layout="flex-row-reverse"
                        label="Copyable Input"
                        description="Read-only input with copy-to-clipboard functionality"
                        className="[&>div]:md:w-1/2 [&>div>div]:md:w-full"
                      >
                        <FormControl_Shadcn_>
                          <Input
                            copy
                            readOnly
                            value={form.getValues('apiKey') || ''}
                            onChange={() => {}}
                            onCopy={() => console.log('Copied to clipboard')}
                          />
                        </FormControl_Shadcn_>
                      </FormItemLayout>
                    )}
                  />
 
                  <Separator className="bg-border -mx-6 w-[calc(100%+3rem)]" />
 
                  {/* Number Input */}
                  <FormField_Shadcn_
                    control={form.control}
                    name="maxConnections"
                    render={({ field }) => (
                      <FormItemLayout
                        layout="flex-row-reverse"
                        label="Number Input"
                        description="Numeric input with min/max validation"
                        className="[&>div]:md:w-1/2"
                      >
                        <FormControl_Shadcn_>
                          <Input_Shadcn_
                            {...field}
                            type="number"
                            min={1}
                            max={1000}
                            onChange={(e) => field.onChange(Number(e.target.value))}
                          />
                        </FormControl_Shadcn_>
                      </FormItemLayout>
                    )}
                  />
 
                  <Separator className="bg-border -mx-6 w-[calc(100%+3rem)]" />
 
                  {/* Input with Units */}
                  <FormField_Shadcn_
                    control={form.control}
                    name="duration"
                    render={({ field }) => (
                      <FormItemLayout
                        layout="flex-row-reverse"
                        label="Input with Units"
                        description="Input with additional unit label"
                        className="[&>div]:md:w-1/2"
                      >
                        <FormControl_Shadcn_>
                          <PrePostTab postTab="MB" className="w-full">
                            <Input_Shadcn_ {...field} type="number" min={5} max={30} />
                          </PrePostTab>
                        </FormControl_Shadcn_>
                      </FormItemLayout>
                    )}
                  />
 
                  <Separator className="bg-border -mx-6 w-[calc(100%+3rem)]" />
 
                  {/* Textarea */}
                  <FormField_Shadcn_
                    control={form.control}
                    name="description"
                    render={({ field }) => (
                      <FormItemLayout
                        layout="flex-row-reverse"
                        label="Textarea"
                        description="Multi-line text input for longer content"
                        className="[&>div]:md:w-1/2"
                      >
                        <FormControl_Shadcn_>
                          <Textarea
                            {...field}
                            rows={4}
                            placeholder="Enter multi-line text"
                            className="resize-none"
                          />
                        </FormControl_Shadcn_>
                      </FormItemLayout>
                    )}
                  />
 
                  <Separator className="bg-border -mx-6 w-[calc(100%+3rem)]" />
 
                  {/* Icon Upload */}
                  <FormField_Shadcn_
                    control={form.control}
                    name="description"
                    render={() => (
                      <FormItemLayout
                        layout="flex-row-reverse"
                        label="Icon upload"
                        description="For icons, avatars, or small images with preview"
                        className="[&>div]:md:w-1/2"
                      >
                        <FormControl_Shadcn_>
                          <div className="flex gap-4 items-center">
                            <button
                              type="button"
                              onClick={() => uploadButtonRef.current?.click()}
                              className="flex items-center justify-center h-10 w-10 shrink-0 text-foreground-lighter hover:text-foreground-light overflow-hidden rounded-full bg-cover border hover:border-strong"
                              style={{
                                backgroundImage: logoUrl ? `url("${logoUrl}")` : 'none',
                              }}
                            >
                              {!logoUrl && <Upload size={14} />}
                            </button>
                            <div className="flex gap-2 items-center">
                              <Button
                                type="default"
                                size="tiny"
                                icon={<Upload size={14} />}
                                onClick={() => uploadButtonRef.current?.click()}
                              >
                                Upload
                              </Button>
                              {logoUrl && (
                                <Button
                                  type="default"
                                  size="tiny"
                                  icon={<Trash2 size={12} />}
                                  onClick={() => {
                                    setLogoFile(undefined)
                                    setLogoUrl(undefined)
                                  }}
                                />
                              )}
                            </div>
                            <input
                              type="file"
                              ref={uploadButtonRef}
                              className="hidden"
                              accept="image/png, image/jpeg"
                              onChange={(e) => {
                                const files = e.target.files
                                if (files && files.length > 0) {
                                  const file = files[0]
                                  setLogoFile(file)
                                  setLogoUrl(URL.createObjectURL(file))
                                  e.target.value = ''
                                }
                              }}
                            />
                          </div>
                        </FormControl_Shadcn_>
                      </FormItemLayout>
                    )}
                  />
 
                  <Separator className="bg-border -mx-6 w-[calc(100%+3rem)]" />
 
                  {/* File Upload */}
                  <FormField_Shadcn_
                    control={form.control}
                    name="description"
                    render={() => (
                      <FormItemLayout
                        layout="flex-row-reverse"
                        label="File Upload"
                        description="Drag-and-drop or select files for upload"
                        className="[&>div]:md:w-1/2 [&>div>div]:md:w-full"
                      >
                        <FormControl_Shadcn_>
                          <div
                            className={`border-2 rounded-lg p-6 text-center bg-muted transition-colors duration-300 ${
                              isDragging
                                ? 'border-strong border-dashed bg-muted'
                                : 'border-border border-dashed'
                            }`}
                            onDragOver={(e) => {
                              e.preventDefault()
                              setIsDragging(true)
                            }}
                            onDragLeave={() => setIsDragging(false)}
                            onDrop={(e) => {
                              e.preventDefault()
                              setIsDragging(false)
                              const files = Array.from(e.dataTransfer.files)
                              setUploadedFiles((prev) => [...prev, ...files])
                            }}
                          >
                            <input
                              type="file"
                              ref={fileUploadRef}
                              className="hidden"
                              multiple
                              onChange={(e) => {
                                const files = e.target.files
                                if (files) {
                                  setUploadedFiles((prev) => [...prev, ...Array.from(files)])
                                }
                                e.target.value = ''
                              }}
                            />
                            <div className="flex flex-col items-center gap-y-2">
                              <Upload size={20} className="text-foreground-lighter" />
                              <p className="text-sm text-foreground-light">
                                {uploadedFiles.length > 0
                                  ? `${uploadedFiles.length} file${uploadedFiles.length > 1 ? 's' : ''} selected`
                                  : 'Upload files'}
                              </p>
                              <p className="text-xs text-foreground-lighter">
                                Drag and drop or{' '}
                                <button
                                  type="button"
                                  onClick={() => fileUploadRef.current?.click()}
                                  className="underline cursor-pointer hover:text-foreground-light"
                                >
                                  select files
                                </button>{' '}
                                to upload
                              </p>
                              {uploadedFiles.length > 0 && (
                                <div className="mt-4 w-full space-y-2">
                                  {uploadedFiles.map((file, idx) => (
                                    <div
                                      key={`${file.name}-${idx}`}
                                      className="flex items-center justify-between gap-2 p-2 bg rounded border"
                                    >
                                      <span className="text-sm text-foreground-light truncate flex-1">
                                        {file.name}
                                      </span>
                                      <Button
                                        type="default"
                                        size="tiny"
                                        icon={<Trash2 size={12} />}
                                        onClick={() => {
                                          setUploadedFiles((prev) =>
                                            prev.filter((_, i) => i !== idx)
                                          )
                                        }}
                                      />
                                    </div>
                                  ))}
                                </div>
                              )}
                            </div>
                          </div>
                        </FormControl_Shadcn_>
                      </FormItemLayout>
                    )}
                  />
 
                  <Separator className="bg-border -mx-6 w-[calc(100%+3rem)]" />
 
                  {/* Switch */}
                  <FormField_Shadcn_
                    control={form.control}
                    name="enableFeature"
                    render={({ field }) => (
                      <FormItemLayout
                        layout="flex-row-reverse"
                        label="Switch"
                        description="Toggle for boolean on/off states"
                      >
                        <FormControl_Shadcn_>
                          <Switch checked={field.value} onCheckedChange={field.onChange} />
                        </FormControl_Shadcn_>
                      </FormItemLayout>
                    )}
                  />
 
                  <Separator className="bg-border -mx-6 w-[calc(100%+3rem)]" />
 
                  {/* Checkbox */}
                  <FormItemLayout
                    layout="flex-row-reverse"
                    label="Checkbox"
                    description="Boolean values or multiple selections"
                    className="[&>div]:md:w-1/2"
                  >
                    <div className="w-full flex flex-col gap-4">
                      <FormField_Shadcn_
                        control={form.control}
                        name="enableRls"
                        render={({ field }) => (
                          <div className="flex items-center w-full justify-start space-x-2">
                            <FormControl_Shadcn_>
                              <Checkbox_Shadcn_
                                id="enable-rls"
                                checked={field.value}
                                onCheckedChange={field.onChange}
                              />
                            </FormControl_Shadcn_>
                            <label
                              htmlFor="enable-rls"
                              className="text-sm text-foreground-light leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70 cursor-pointer"
                            >
                              Enable Row Level Security
                            </label>
                          </div>
                        )}
                      />
                      <FormField_Shadcn_
                        control={form.control}
                        name="enableNotifications"
                        render={({ field }) => (
                          <div className="flex items-center w-full justify-start space-x-2">
                            <FormControl_Shadcn_>
                              <Checkbox_Shadcn_
                                id="enable-notifications"
                                checked={field.value}
                                onCheckedChange={field.onChange}
                              />
                            </FormControl_Shadcn_>
                            <label
                              htmlFor="enable-notifications"
                              className="text-sm text-foreground-light leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70 cursor-pointer"
                            >
                              Enable email notifications
                            </label>
                          </div>
                        )}
                      />
                      <FormField_Shadcn_
                        control={form.control}
                        name="enableAnalytics"
                        render={({ field }) => (
                          <div className="flex items-center w-full justify-start space-x-2">
                            <FormControl_Shadcn_>
                              <Checkbox_Shadcn_
                                id="enable-analytics"
                                checked={field.value}
                                onCheckedChange={field.onChange}
                              />
                            </FormControl_Shadcn_>
                            <label
                              htmlFor="enable-analytics"
                              className="text-sm text-foreground-light leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70 cursor-pointer"
                            >
                              Enable analytics tracking
                            </label>
                          </div>
                        )}
                      />
                    </div>
                  </FormItemLayout>
 
                  <Separator className="bg-border -mx-6 w-[calc(100%+3rem)]" />
 
                  {/* Select */}
                  <FormField_Shadcn_
                    control={form.control}
                    name="region"
                    render={({ field }) => (
                      <FormItemLayout
                        layout="flex-row-reverse"
                        label="Select (Dropdown)"
                        description="Single selection from a list of options"
                        className="[&>div]:md:w-1/2"
                      >
                        <FormControl_Shadcn_>
                          <Select_Shadcn_ value={field.value} onValueChange={field.onChange}>
                            <SelectTrigger_Shadcn_>
                              <SelectValue_Shadcn_ placeholder="Select an option" />
                            </SelectTrigger_Shadcn_>
                            <SelectContent_Shadcn_>
                              <SelectItem_Shadcn_ value="us-east-1">
                                US East (N. Virginia)
                              </SelectItem_Shadcn_>
                              <SelectItem_Shadcn_ value="us-west-2">
                                US West (Oregon)
                              </SelectItem_Shadcn_>
                              <SelectItem_Shadcn_ value="eu-west-1">
                                EU West (Ireland)
                              </SelectItem_Shadcn_>
                            </SelectContent_Shadcn_>
                          </Select_Shadcn_>
                        </FormControl_Shadcn_>
                      </FormItemLayout>
                    )}
                  />
 
                  <Separator className="bg-border -mx-6 w-[calc(100%+3rem)]" />
 
                  {/* Multi-Select */}
                  <FormField_Shadcn_
                    control={form.control}
                    name="schemas"
                    render={({ field }) => (
                      <FormItemLayout
                        layout="flex-row-reverse"
                        label="Multi-Select"
                        description="Multiple selection from a list"
                        className="[&>div]:md:w-1/2 [&>div>div]:md:w-full"
                      >
                        <MultiSelector
                          onValuesChange={field.onChange}
                          values={field.value}
                          size="small"
                        >
                          <MultiSelectorTrigger
                            mode="inline-combobox"
                            label="Select options..."
                            badgeLimit="wrap"
                            showIcon={false}
                            deletableBadge
                            className="w-full"
                          />
                          <MultiSelectorContent>
                            <MultiSelectorList>
                              <MultiSelectorItem value="public">public</MultiSelectorItem>
                              <MultiSelectorItem value="auth">auth</MultiSelectorItem>
                              <MultiSelectorItem value="storage">storage</MultiSelectorItem>
                            </MultiSelectorList>
                          </MultiSelectorContent>
                        </MultiSelector>
                      </FormItemLayout>
                    )}
                  />
 
                  <Separator className="bg-border -mx-6 w-[calc(100%+3rem)]" />
 
                  {/* Radio Group */}
                  <FormField_Shadcn_
                    control={form.control}
                    name="queueType"
                    render={({ field }) => (
                      <FormItemLayout
                        layout="flex-row-reverse"
                        label="Radio Group"
                        description="Single selection from multiple options"
                        className="[&>div]:md:w-1/2"
                      >
                        <FormControl_Shadcn_>
                          <RadioGroupStacked value={field.value} onValueChange={field.onChange}>
                            <RadioGroupStackedItem
                              value="basic"
                              label="Option 1"
                              description="First option description"
                            />
                            <RadioGroupStackedItem
                              value="partitioned"
                              label="Option 2"
                              description="Second option description"
                            />
                          </RadioGroupStacked>
                        </FormControl_Shadcn_>
                      </FormItemLayout>
                    )}
                  />
 
                  <Separator className="bg-border -mx-6 w-[calc(100%+3rem)]" />
 
                  {/* Date Picker */}
                  <FormField_Shadcn_
                    control={form.control}
                    name="expiryDate"
                    render={({ field }) => (
                      <FormItemLayout
                        layout="flex-row-reverse"
                        label="Date Picker"
                        description="Date selection with calendar popover"
                        className="[&>div]:md:w-1/2"
                      >
                        <FormControl_Shadcn_>
                          <Popover_Shadcn_>
                            <PopoverTrigger_Shadcn_ asChild>
                              <Button
                                type="outline"
                                className="w-full justify-start text-left font-normal px-3 py-4"
                                icon={<CalendarIcon className="h-4 w-4" />}
                              >
                                {field.value ? format(field.value, 'PPP') : 'Pick a date'}
                              </Button>
                            </PopoverTrigger_Shadcn_>
                            <PopoverContent_Shadcn_ className="w-auto p-0" align="start">
                              <Calendar
                                mode="single"
                                selected={field.value}
                                onSelect={field.onChange}
                                initialFocus
                              />
                            </PopoverContent_Shadcn_>
                          </Popover_Shadcn_>
                        </FormControl_Shadcn_>
                      </FormItemLayout>
                    )}
                  />
 
                  <Separator className="bg-border -mx-6 w-[calc(100%+3rem)]" />
 
                  {/* Field Array */}
                  <FormField_Shadcn_
                    control={form.control}
                    name="redirectUris"
                    render={() => (
                      <FormItemLayout
                        layout="flex-row-reverse"
                        label="Field Array"
                        description="Dynamic list for adding/removing items"
                        className="[&>div]:md:w-1/2"
                      >
                        <div className="space-y-2 w-full">
                          {fields.map((field, index) => (
                            <FormField_Shadcn_
                              key={field.id}
                              control={form.control}
                              name={`redirectUris.${index}.value`}
                              render={({ field: inputField }) => (
                                <div className="flex gap-2">
                                  <FormControl_Shadcn_>
                                    <Input_Shadcn_
                                      {...inputField}
                                      placeholder="https://example.com/callback"
                                    />
                                  </FormControl_Shadcn_>
                                  {fields.length > 1 && (
                                    <Button
                                      type="default"
                                      size="tiny"
                                      icon={<Trash2 size={12} />}
                                      onClick={() => remove(index)}
                                    />
                                  )}
                                </div>
                              )}
                            />
                          ))}
                          <Button
                            type="default"
                            icon={<Plus />}
                            onClick={() => append({ value: '' })}
                          >
                            Add redirect URI
                          </Button>
                        </div>
                      </FormItemLayout>
                    )}
                  />
 
                  <Separator className="bg-border -mx-6 w-[calc(100%+3rem)]" />
 
                  {/* Action Field */}
                  <FormItemLayout
                    layout="flex-row-reverse"
                    label="Action Field"
                    description="Button or link for navigation or performable actions"
                    className="[&>div]:md:w-1/2"
                  >
                    <div className="flex gap-2 items-center">
                      <Button
                        type="default"
                        icon={<ExternalLink size={14} />}
                        onClick={() => console.log('Action performed')}
                      >
                        View documentation
                      </Button>
                      <Button type="default" onClick={() => console.log('Reset action')}>
                        Reset API key
                      </Button>
                    </div>
                  </FormItemLayout>
                </CardContent>
                <CardFooter className="justify-end space-x-2">
                  {form.formState.isDirty && (
                    <Button type="default" onClick={() => form.reset()}>
                      Cancel
                    </Button>
                  )}
                  <Button type="primary" htmlType="submit" disabled={!form.formState.isDirty}>
                    Save changes
                  </Button>
                </CardFooter>
              </Card>
            </form>
          </Form_Shadcn_>
        </PageSectionContent>
      </PageSection>
    </div>
  )
}

Side Panel

Forms in side panels (Sheets) use FormItemLayout with layout="horizontal" on wider panels and layout="vertical" on panels with a size of sm or below. The form is typically wrapped in a Sheet component.

Loading...
import { zodResolver } from '@hookform/resolvers/zod'
import { format } from 'date-fns'
import { CalendarIcon, ExternalLink, Plus, Trash2, Upload } from 'lucide-react'
import { useRef, useState } from 'react'
import { useFieldArray, useForm } from 'react-hook-form'
import * as z from 'zod'
 
import {
  Button,
  Calendar,
  Checkbox_Shadcn_,
  FormControl_Shadcn_,
  FormField_Shadcn_,
  Form_Shadcn_,
  Input_Shadcn_,
  Popover_Shadcn_,
  PopoverContent_Shadcn_,
  PopoverTrigger_Shadcn_,
  RadioGroupStacked,
  RadioGroupStackedItem,
  Select_Shadcn_,
  SelectContent_Shadcn_,
  SelectItem_Shadcn_,
  SelectTrigger_Shadcn_,
  SelectValue_Shadcn_,
  Separator,
  Sheet,
  SheetContent,
  SheetFooter,
  SheetHeader,
  SheetSection,
  SheetTitle,
  Switch,
  Textarea,
  PrePostTab,
} from 'ui'
import { FormItemLayout } from 'ui-patterns/form/FormItemLayout/FormItemLayout'
import { Input } from 'ui-patterns/DataInputs/Input'
import {
  MultiSelector,
  MultiSelectorContent,
  MultiSelectorItem,
  MultiSelectorList,
  MultiSelectorTrigger,
} from 'ui-patterns/multi-select'
 
const formSchema = z.object({
  name: z.string().min(1, 'Name is required'),
  description: z.string().optional(),
  maxConnections: z.number().min(1).max(1000),
  enableFeature: z.boolean(),
  enableRls: z.boolean(),
  enableNotifications: z.boolean(),
  enableAnalytics: z.boolean(),
  region: z.string().min(1, 'Region is required'),
  schemas: z.array(z.string()).min(1, 'At least one schema is required'),
  queueType: z.enum(['basic', 'partitioned']),
  expiryDate: z.date().optional(),
  password: z.string().min(8, 'Password must be at least 8 characters'),
  duration: z.number().min(5).max(30),
  redirectUris: z.array(z.object({ value: z.string().url('Must be a valid URL') })),
  apiKey: z.string().optional(),
})
 
const fakeApiKey = 'sk_live_51H3x4mpl3_4nd_53cur3_k3y_1234567890'
 
export function FormPatternsSidePanel() {
  const [open, setOpen] = useState(false)
  const uploadButtonRef = useRef<HTMLInputElement>(null)
  const fileUploadRef = useRef<HTMLInputElement>(null)
  const [logoFile, setLogoFile] = useState<File>()
  const [logoUrl, setLogoUrl] = useState<string>()
  const [uploadedFiles, setUploadedFiles] = useState<File[]>([])
  const [isDragging, setIsDragging] = useState(false)
 
  const form = useForm<z.infer<typeof formSchema>>({
    resolver: zodResolver(formSchema),
    defaultValues: {
      name: '',
      description: '',
      maxConnections: 10,
      enableFeature: false,
      enableRls: true,
      enableNotifications: false,
      enableAnalytics: true,
      region: '',
      schemas: ['public'],
      queueType: 'basic',
      expiryDate: undefined,
      password: '',
      duration: 10,
      redirectUris: [{ value: '' }],
      apiKey: fakeApiKey,
    },
  })
 
  const { fields, append, remove } = useFieldArray({
    control: form.control,
    name: 'redirectUris',
  })
 
  function onSubmit(values: z.infer<typeof formSchema>) {
    console.log(values)
    setOpen(false)
  }
 
  const formId = 'sidepanel-form'
 
  return (
    <>
      <Button type="primary" onClick={() => setOpen(true)}>
        Open form panel
      </Button>
      <Sheet open={open} onOpenChange={setOpen}>
        <SheetContent size="lg" className="flex flex-col gap-0">
          <SheetHeader>
            <SheetTitle>Create Configuration</SheetTitle>
          </SheetHeader>
          <Form_Shadcn_ {...form}>
            <form
              id={formId}
              onSubmit={form.handleSubmit(onSubmit)}
              className="overflow-auto flex-grow px-0"
            >
              {/* Text Input */}
              <SheetSection>
                <FormField_Shadcn_
                  control={form.control}
                  name="name"
                  render={({ field }) => (
                    <FormItemLayout
                      layout="horizontal"
                      label="Text Input"
                      description="Single-line text entry for short values"
                    >
                      <FormControl_Shadcn_ className="col-span-6">
                        <Input_Shadcn_ {...field} placeholder="Enter text" />
                      </FormControl_Shadcn_>
                    </FormItemLayout>
                  )}
                />
              </SheetSection>
 
              <Separator className="-mx-5 w-[calc(100%+2.5rem)]" />
 
              {/* Password Input */}
              <SheetSection>
                <FormField_Shadcn_
                  control={form.control}
                  name="password"
                  render={({ field }) => (
                    <FormItemLayout
                      layout="horizontal"
                      label="Password Input"
                      description="Masked input for secure text entry"
                    >
                      <FormControl_Shadcn_ className="col-span-6">
                        <Input_Shadcn_ {...field} type="password" placeholder="Enter password" />
                      </FormControl_Shadcn_>
                    </FormItemLayout>
                  )}
                />
              </SheetSection>
 
              <Separator className="-mx-5 w-[calc(100%+2.5rem)]" />
 
              {/* Copyable Input */}
              <SheetSection>
                <FormField_Shadcn_
                  control={form.control}
                  name="apiKey"
                  render={() => (
                    <FormItemLayout
                      layout="horizontal"
                      label="Copyable Input"
                      description="Read-only input with copy-to-clipboard functionality"
                    >
                      <FormControl_Shadcn_ className="col-span-6">
                        <Input
                          copy
                          readOnly
                          className="input-mono"
                          value={form.getValues('apiKey') || ''}
                          onChange={() => {}}
                          onCopy={() => console.log('Copied to clipboard')}
                        />
                      </FormControl_Shadcn_>
                    </FormItemLayout>
                  )}
                />
              </SheetSection>
 
              <Separator className="-mx-5 w-[calc(100%+2.5rem)]" />
 
              {/* Number Input */}
              <SheetSection>
                <FormField_Shadcn_
                  control={form.control}
                  name="maxConnections"
                  render={({ field }) => (
                    <FormItemLayout
                      layout="horizontal"
                      label="Number Input"
                      description="Numeric input with min/max validation"
                    >
                      <FormControl_Shadcn_ className="col-span-6">
                        <Input_Shadcn_
                          {...field}
                          type="number"
                          min={1}
                          max={1000}
                          onChange={(e) => field.onChange(Number(e.target.value))}
                        />
                      </FormControl_Shadcn_>
                    </FormItemLayout>
                  )}
                />
              </SheetSection>
 
              <Separator className="-mx-5 w-[calc(100%+2.5rem)]" />
 
              {/* Input with Units */}
              <SheetSection>
                <FormField_Shadcn_
                  control={form.control}
                  name="duration"
                  render={({ field }) => (
                    <FormItemLayout
                      layout="horizontal"
                      label="Input with Units"
                      description="Input with additional unit label"
                    >
                      <FormControl_Shadcn_ className="col-span-6">
                        <PrePostTab postTab="MB" className="w-full">
                          <Input_Shadcn_ {...field} type="number" min={5} max={30} />
                        </PrePostTab>
                      </FormControl_Shadcn_>
                    </FormItemLayout>
                  )}
                />
              </SheetSection>
 
              <Separator className="-mx-5 w-[calc(100%+2.5rem)]" />
 
              {/* Textarea */}
              <SheetSection>
                <FormField_Shadcn_
                  control={form.control}
                  name="description"
                  render={({ field }) => (
                    <FormItemLayout
                      layout="horizontal"
                      label="Textarea"
                      description="Multi-line text input for longer content"
                    >
                      <FormControl_Shadcn_ className="col-span-6">
                        <Textarea
                          {...field}
                          rows={3}
                          placeholder="Enter multi-line text"
                          className="resize-none"
                        />
                      </FormControl_Shadcn_>
                    </FormItemLayout>
                  )}
                />
              </SheetSection>
 
              <Separator className="-mx-5 w-[calc(100%+2.5rem)]" />
 
              {/* Icon Upload */}
              <SheetSection>
                <FormField_Shadcn_
                  control={form.control}
                  name="description"
                  render={() => (
                    <FormItemLayout
                      layout="horizontal"
                      label="Icon upload"
                      description="For icons, avatars, or small images with preview"
                    >
                      <FormControl_Shadcn_ className="col-span-6">
                        <div className="flex gap-4 items-center">
                          <button
                            type="button"
                            onClick={() => uploadButtonRef.current?.click()}
                            className="flex items-center justify-center h-10 w-10 shrink-0 text-foreground-lighter hover:text-foreground-light overflow-hidden rounded-full bg-cover border hover:border-strong"
                            style={{
                              backgroundImage: logoUrl ? `url("${logoUrl}")` : 'none',
                            }}
                          >
                            {!logoUrl && <Upload size={14} />}
                          </button>
                          <div className="flex gap-2 items-center">
                            <Button
                              type="default"
                              size="tiny"
                              icon={<Upload size={14} />}
                              onClick={() => uploadButtonRef.current?.click()}
                            >
                              Upload
                            </Button>
                            {logoUrl && (
                              <Button
                                type="default"
                                size="tiny"
                                icon={<Trash2 size={12} />}
                                onClick={() => {
                                  setLogoFile(undefined)
                                  setLogoUrl(undefined)
                                }}
                              />
                            )}
                          </div>
                          <input
                            type="file"
                            ref={uploadButtonRef}
                            className="hidden"
                            accept="image/png, image/jpeg"
                            onChange={(e) => {
                              const files = e.target.files
                              if (files && files.length > 0) {
                                const file = files[0]
                                setLogoFile(file)
                                setLogoUrl(URL.createObjectURL(file))
                                e.target.value = ''
                              }
                            }}
                          />
                        </div>
                      </FormControl_Shadcn_>
                    </FormItemLayout>
                  )}
                />
              </SheetSection>
 
              <Separator className="-mx-5 w-[calc(100%+2.5rem)]" />
 
              {/* File Upload */}
              <SheetSection>
                <FormField_Shadcn_
                  control={form.control}
                  name="description"
                  render={() => (
                    <FormItemLayout
                      layout="horizontal"
                      label="File Upload"
                      description="Drag-and-drop or select files for upload"
                    >
                      <FormControl_Shadcn_ className="col-span-6">
                        <div
                          className={`border-2 rounded-lg p-6 text-center bg-muted transition-colors duration-300 ${
                            isDragging
                              ? 'border-strong border-dashed bg-muted'
                              : 'border-border border-dashed'
                          }`}
                          onDragOver={(e) => {
                            e.preventDefault()
                            setIsDragging(true)
                          }}
                          onDragLeave={() => setIsDragging(false)}
                          onDrop={(e) => {
                            e.preventDefault()
                            setIsDragging(false)
                            const files = Array.from(e.dataTransfer.files)
                            setUploadedFiles((prev) => [...prev, ...files])
                          }}
                        >
                          <input
                            type="file"
                            ref={fileUploadRef}
                            className="hidden"
                            multiple
                            onChange={(e) => {
                              const files = e.target.files
                              if (files) {
                                setUploadedFiles((prev) => [...prev, ...Array.from(files)])
                              }
                              e.target.value = ''
                            }}
                          />
                          <div className="flex flex-col items-center gap-y-2">
                            <Upload size={20} className="text-foreground-lighter" />
                            <p className="text-sm text-foreground-light">
                              {uploadedFiles.length > 0
                                ? `${uploadedFiles.length} file${uploadedFiles.length > 1 ? 's' : ''} selected`
                                : 'Upload files'}
                            </p>
                            <p className="text-xs text-foreground-lighter">
                              Drag and drop or{' '}
                              <button
                                type="button"
                                onClick={() => fileUploadRef.current?.click()}
                                className="underline cursor-pointer hover:text-foreground-light"
                              >
                                select files
                              </button>{' '}
                              to upload
                            </p>
                            {uploadedFiles.length > 0 && (
                              <div className="mt-4 w-full space-y-2">
                                {uploadedFiles.map((file, idx) => (
                                  <div
                                    key={`${file.name}-${idx}`}
                                    className="flex items-center justify-between gap-2 p-2 bg rounded border"
                                  >
                                    <span className="text-sm text-foreground-light truncate flex-1">
                                      {file.name}
                                    </span>
                                    <Button
                                      type="default"
                                      size="tiny"
                                      icon={<Trash2 size={12} />}
                                      onClick={() => {
                                        setUploadedFiles((prev) => prev.filter((_, i) => i !== idx))
                                      }}
                                    />
                                  </div>
                                ))}
                              </div>
                            )}
                          </div>
                        </div>
                      </FormControl_Shadcn_>
                    </FormItemLayout>
                  )}
                />
              </SheetSection>
 
              <Separator className="-mx-5 w-[calc(100%+2.5rem)]" />
 
              {/* Switch */}
              <SheetSection>
                <FormField_Shadcn_
                  control={form.control}
                  name="enableFeature"
                  render={({ field }) => (
                    <FormItemLayout
                      layout="horizontal"
                      label="Switch"
                      description="Toggle for boolean on/off states"
                    >
                      <FormControl_Shadcn_ className="col-span-6">
                        <Switch checked={field.value} onCheckedChange={field.onChange} />
                      </FormControl_Shadcn_>
                    </FormItemLayout>
                  )}
                />
              </SheetSection>
 
              {/* Checkbox */}
              <SheetSection>
                <FormItemLayout
                  layout="horizontal"
                  label="Checkbox"
                  description="Boolean values or multiple selections"
                >
                  <div className="col-span-6 w-full flex flex-col gap-4">
                    <FormField_Shadcn_
                      control={form.control}
                      name="enableRls"
                      render={({ field }) => (
                        <div className="flex items-center w-full justify-start space-x-2">
                          <FormControl_Shadcn_>
                            <Checkbox_Shadcn_
                              id="enable-rls"
                              checked={field.value}
                              onCheckedChange={field.onChange}
                            />
                          </FormControl_Shadcn_>
                          <label
                            htmlFor="enable-rls"
                            className="text-sm text-foreground-light leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70 cursor-pointer"
                          >
                            Enable Row Level Security
                          </label>
                        </div>
                      )}
                    />
                    <FormField_Shadcn_
                      control={form.control}
                      name="enableNotifications"
                      render={({ field }) => (
                        <div className="flex items-center w-full justify-start space-x-2">
                          <FormControl_Shadcn_>
                            <Checkbox_Shadcn_
                              id="enable-notifications"
                              checked={field.value}
                              onCheckedChange={field.onChange}
                            />
                          </FormControl_Shadcn_>
                          <label
                            htmlFor="enable-notifications"
                            className="text-sm text-foreground-light leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70 cursor-pointer"
                          >
                            Enable email notifications
                          </label>
                        </div>
                      )}
                    />
                    <FormField_Shadcn_
                      control={form.control}
                      name="enableAnalytics"
                      render={({ field }) => (
                        <div className="flex items-center w-full justify-start space-x-2">
                          <FormControl_Shadcn_>
                            <Checkbox_Shadcn_
                              id="enable-analytics"
                              checked={field.value}
                              onCheckedChange={field.onChange}
                            />
                          </FormControl_Shadcn_>
                          <label
                            htmlFor="enable-analytics"
                            className="text-sm text-foreground-light leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70 cursor-pointer"
                          >
                            Enable analytics tracking
                          </label>
                        </div>
                      )}
                    />
                  </div>
                </FormItemLayout>
              </SheetSection>
 
              <Separator className="-mx-5 w-[calc(100%+2.5rem)]" />
 
              {/* Select */}
              <SheetSection>
                <FormField_Shadcn_
                  control={form.control}
                  name="region"
                  render={({ field }) => (
                    <FormItemLayout
                      layout="horizontal"
                      label="Select (Dropdown)"
                      description="Single selection from a list of options"
                    >
                      <FormControl_Shadcn_ className="col-span-6">
                        <Select_Shadcn_ value={field.value} onValueChange={field.onChange}>
                          <SelectTrigger_Shadcn_>
                            <SelectValue_Shadcn_ placeholder="Select an option" />
                          </SelectTrigger_Shadcn_>
                          <SelectContent_Shadcn_>
                            <SelectItem_Shadcn_ value="us-east-1">
                              US East (N. Virginia)
                            </SelectItem_Shadcn_>
                            <SelectItem_Shadcn_ value="us-west-2">
                              US West (Oregon)
                            </SelectItem_Shadcn_>
                            <SelectItem_Shadcn_ value="eu-west-1">
                              EU West (Ireland)
                            </SelectItem_Shadcn_>
                          </SelectContent_Shadcn_>
                        </Select_Shadcn_>
                      </FormControl_Shadcn_>
                    </FormItemLayout>
                  )}
                />
              </SheetSection>
 
              <Separator className="-mx-5 w-[calc(100%+2.5rem)]" />
 
              {/* Multi-Select */}
              <SheetSection>
                <FormField_Shadcn_
                  control={form.control}
                  name="schemas"
                  render={({ field }) => (
                    <FormItemLayout
                      layout="horizontal"
                      label="Multi-Select"
                      description="Multiple selection from a list"
                    >
                      <div className="col-span-6">
                        <MultiSelector
                          onValuesChange={field.onChange}
                          values={field.value}
                          size="small"
                          className="w-full"
                        >
                          <MultiSelectorTrigger
                            mode="inline-combobox"
                            label="Select options..."
                            badgeLimit="wrap"
                            showIcon={false}
                            deletableBadge
                            className="w-full !min-w-lg"
                          />
                          <MultiSelectorContent>
                            <MultiSelectorList>
                              <MultiSelectorItem value="public">public</MultiSelectorItem>
                              <MultiSelectorItem value="auth">auth</MultiSelectorItem>
                              <MultiSelectorItem value="storage">storage</MultiSelectorItem>
                            </MultiSelectorList>
                          </MultiSelectorContent>
                        </MultiSelector>
                      </div>
                    </FormItemLayout>
                  )}
                />
              </SheetSection>
 
              <Separator className="-mx-5 w-[calc(100%+2.5rem)]" />
 
              {/* Radio Group */}
              <SheetSection>
                <FormField_Shadcn_
                  control={form.control}
                  name="queueType"
                  render={({ field }) => (
                    <FormItemLayout
                      layout="horizontal"
                      label="Radio Group"
                      description="Single selection from multiple options"
                    >
                      <div className="col-span-6">
                        <RadioGroupStacked value={field.value} onValueChange={field.onChange}>
                          <RadioGroupStackedItem
                            value="basic"
                            label="Option 1"
                            description="First option description"
                          />
                          <RadioGroupStackedItem
                            value="partitioned"
                            label="Option 2"
                            description="Second option description"
                          />
                        </RadioGroupStacked>
                      </div>
                    </FormItemLayout>
                  )}
                />
              </SheetSection>
 
              <Separator className="-mx-5 w-[calc(100%+2.5rem)]" />
 
              {/* Date Picker */}
              <SheetSection>
                <FormField_Shadcn_
                  control={form.control}
                  name="expiryDate"
                  render={({ field }) => (
                    <FormItemLayout
                      layout="horizontal"
                      label="Date Picker"
                      description="Date selection with calendar popover"
                    >
                      <FormControl_Shadcn_ className="col-span-6">
                        <Popover_Shadcn_>
                          <PopoverTrigger_Shadcn_ asChild>
                            <Button
                              type="outline"
                              className="w-full justify-start text-left font-normal px-3 py-4"
                              icon={<CalendarIcon className="h-4 w-4" />}
                            >
                              {field.value ? format(field.value, 'PPP') : 'Pick a date'}
                            </Button>
                          </PopoverTrigger_Shadcn_>
                          <PopoverContent_Shadcn_ className="w-auto p-0" align="start">
                            <Calendar
                              mode="single"
                              selected={field.value}
                              onSelect={field.onChange}
                              initialFocus
                            />
                          </PopoverContent_Shadcn_>
                        </Popover_Shadcn_>
                      </FormControl_Shadcn_>
                    </FormItemLayout>
                  )}
                />
              </SheetSection>
 
              <Separator className="-mx-5 w-[calc(100%+2.5rem)]" />
 
              {/* Field Array */}
              <SheetSection>
                <FormField_Shadcn_
                  control={form.control}
                  name="redirectUris"
                  render={() => (
                    <FormItemLayout
                      layout="horizontal"
                      label="Field Array"
                      description="Dynamic list for adding/removing items"
                    >
                      <div className="col-span-6 space-y-2">
                        {fields.map((field, index) => (
                          <FormField_Shadcn_
                            key={field.id}
                            control={form.control}
                            name={`redirectUris.${index}.value`}
                            render={({ field: inputField }) => (
                              <div className="flex gap-2">
                                <FormControl_Shadcn_>
                                  <Input_Shadcn_
                                    {...inputField}
                                    placeholder="https://example.com/callback"
                                  />
                                </FormControl_Shadcn_>
                                {fields.length > 1 && (
                                  <Button
                                    type="default"
                                    size="tiny"
                                    icon={<Trash2 size={12} />}
                                    onClick={() => remove(index)}
                                  />
                                )}
                              </div>
                            )}
                          />
                        ))}
                        <Button
                          type="default"
                          icon={<Plus />}
                          onClick={() => append({ value: '' })}
                        >
                          Add redirect URI
                        </Button>
                      </div>
                    </FormItemLayout>
                  )}
                />
              </SheetSection>
 
              <Separator className="-mx-5 w-[calc(100%+2.5rem)]" />
 
              {/* Action Field */}
              <SheetSection>
                <FormItemLayout
                  layout="horizontal"
                  label="Action Field"
                  description="Button or link for navigation or performable actions"
                >
                  <div className="col-span-6 flex gap-2 items-center">
                    <Button
                      type="default"
                      icon={<ExternalLink size={14} />}
                      onClick={() => console.log('Action performed')}
                    >
                      View documentation
                    </Button>
                    <Button type="default" onClick={() => console.log('Reset action')}>
                      Reset API key
                    </Button>
                  </div>
                </FormItemLayout>
              </SheetSection>
            </form>
          </Form_Shadcn_>
          <SheetFooter>
            <Button
              type="default"
              onClick={() => {
                form.reset()
                setOpen(false)
              }}
            >
              Cancel
            </Button>
            <Button type="primary" form={formId} htmlType="submit">
              Create
            </Button>
          </SheetFooter>
        </SheetContent>
      </Sheet>
    </>
  )
}

Best Practices

  1. Always use FormItemLayout: Use FormItemLayout instead of manually composing FormItem, FormLabel, FormMessage, and FormDescription.

  2. Layout selection:

    • Use layout="flex-row-reverse" for page layouts (horizontal alignment)
    • Use layout="horizontal" for side panels with more width
    • Use layout="vertical" for side panels with limited width
  3. Wrap inputs in FormControlShadcn: Always wrap form inputs with FormControl_Shadcn_ to ensure proper form integration.

  4. Use Cards for grouping: Wrap form sections in Card components with CardContent and CardFooter for actions.

  5. Handle dirty state: Show cancel buttons and disable save buttons based on form.formState.isDirty.

  6. Error handling: Always use mutations with onSuccess and onError callbacks that show toast notifications.

  7. Loading states: Show loading states on submit buttons using the loading prop.

  8. Form IDs: When submit buttons are outside the form, use a form ID and reference it with the form prop on the button.