shadcn-form-guide

shadcn-form-guide is a command for Cursor from dyeoman2/tanstack-start-template. It costs 0 tokens per session (2,781 once invoked), scanned A, original, MIT.

A command-line guide for building accessible ShadCN forms with TanStack Form, including field structure, input components, validation, and Convex mutations.

In plain words
What is it for?
Use it when creating or updating React forms that collect data, validate input, and submit mutations.
Why use it?
It provides a consistent way to manage and validate forms instead of designing each form's structure from scratch.

Command for Cursor

Install

Getting it into your agent

One page per mod, every tool's command on it. A separate URL per tool would split the same page into five that compete with each other.

agentmods
npx agentmods add commands/dyeoman2/tanstack-start-template/shadcn-form-guide
Clone the repo
git clone --depth 1 https://github.com/dyeoman2/tanstack-start-template

Made for: Cursor.

Wrote this? Show the measurements

A badge with what this costs and how it scanned, read live from this page, so it follows the numbers instead of freezing them. Markdown for a README, HTML for a documentation site or a project page.

agentmods badge for shadcn-form-guide

README.md
[![agentmods](https://agentmods.dev/badge/commands/dyeoman2/tanstack-start-template/shadcn-form-guide.svg)](https://agentmods.dev/commands/dyeoman2/tanstack-start-template/shadcn-form-guide)
Your own site
<a href="https://agentmods.dev/commands/dyeoman2/tanstack-start-template/shadcn-form-guide"><img src="https://agentmods.dev/badge/commands/dyeoman2/tanstack-start-template/shadcn-form-guide.svg" alt="Measured on agentmods" height="20"></a>
Per session 0 Only the description is in the session, so the agent can decide to use it. The body loads when it is invoked.
When invoked 2,781 The whole file, excluding the scripts and references it only reads on demand.
Security scan A 0 findings. Scan, not verified.
Origin original No closer match found in the catalogue.
Token cost

What it costs to keep this loaded

Counted locally with the o200k_base tokenizer, which is exact for GPT models; Claude uses its own tokenizer and its counts differ. Treat this as one consistent yardstick across the catalogue rather than a bill. Prices are per million input tokens.

ModelPer sessionOnce invoked
Fable 5 $0.00000 $0.02781
Opus 5 $0.00000 $0.01391
Sonnet 5 $0.00000 $0.00556
Haiku 4.5 $0.00000 $0.00278

Measured 3d ago against content hash 529f1b0c7462, method: parsed. Prices are Anthropic first-party input rates as of 2026-08-30, from the pricing page.

Security

Grade A, and why

shadcn-form-guide scanned grade A with 0 findings against 26 rules in 11 categories — prompt injection, anti-refusal, data exfiltration, privilege escalation, supply chain, agent snooping, system-prompt leakage, SSRF and excessive agency — measured 3d ago.

A static scan of the body, not an audit. Every finding is printed with the line that produced it so you can judge whether it matters here. A mod is markdown that instructs an agent; that is exactly why what it instructs is worth reading.

Nothing flagged

None of the 26 patterns this scan looks for appear in this file: no shell pipes, no recursive deletes, no credential paths, no hidden text, no instruction-override or anti-refusal phrasing, no agent-config snooping. That is not a guarantee, it is the absence of the things that are checkable.

.cursor/commands/shadcn-form-guide.md · 440 lines

How it starts

The opening of the file, as written. The whole thing — 440 lines — stays where its author put it; the contents beside it link to each section on GitHub.

ShadCN Form Implementation Guide

This guide demonstrates proper ShadCN form implementation using @tanstack/react-form with the October 2025 component suite (Field, InputGroup, etc.).

Overview

ShadCN forms combine:

  • Field components for semantic structure and accessibility
  • InputGroup components for enhanced input styling
  • @tanstack/react-form for form management and validation

Complete Form Example

import { useForm } from '@tanstack/react-form';
import { useMutation } from 'convex/react';
import { MapPin, Phone, X } from 'lucide-react';
import { useId, useState } from 'react';

// Import ShadCN components
import { Field, FieldLabel } from '~/components/ui/field';
import { Input } from '~/components/ui/input';
import {
  InputGroup,
  InputGroupAddon,
  InputGroupIcon,
  InputGroupInput,
} from '~/components/ui/input-group';
import {
  Select,
  SelectContent,
  SelectItem,
  SelectTrigger,
  SelectValue,
} from '~/components/ui/select';
import { Spinner } from '~/components/ui/spinner';
import { usePhoneFormatter } from '~/hooks/use-phone-formatter';

interface FormData {
  agentType: 'individual' | 'business';
  agentName: string;
  agentTitle: string;
  agentEntityNumber: string;
  agentMailingAddress: string;
  agentMailingCity: string;
  agentMailingState: string;
  agentMailingZipCode: string;
  agentPhoneNumber: string;
  agentEmailAddress: string;
}

export function ExampleForm({ applicationId }: { applicationId: string }) {
  const [errorMessage, setErrorMessage] = useState<string | null>(null);
  const phoneFormatter = usePhoneFormatter();

  // Generate unique IDs for form fields
  const agentNameId = useId();
  const agentTitleId = useId();
  const agentEntityNumberId = useId();
  const agentMailingAddressId = useId();
  const agentMailingCityId = useId();
  const agentMailingStateId = useId();
  const agentMailingZipCodeId = useId();
  const agentPhoneNumberId = useId();
  const agentEmailAddressId = useId();

  const form = useForm({
    defaultValues: {
      agentType: 'individual' as 'individual' | 'business',
      agentName: '',
      agentTitle: '',
      agentEntityNumber: '',
      agentMailingAddress: '',
      agentMailingCity: '',
      agentMailingState: '',
      agentMailingZipCode: '',
      agentPhoneNumber: '',
      agentEmailAddress: '',
    },
    onSubmit: async ({ value }) => {
      setErrorMessage(null);
      // Handle form submission
      mutation.mutate(value);
    },
  });

  const mutation = useMutation({
    mutationFn: async (data: FormData) => {
      // Server function call
      return await serverFunction({ data });
    },
    onSuccess: (result) => {
      // Reset form
      form.reset();

      // Handle success
      console.log('Form submitted successfully', result);
    },
    onError: (error: Error) => {
      setErrorMessage(error.message || 'Submission failed');
    },
  });

  const [agentType, setAgentType] = useState<'individual' | 'business'>('individual');
  const isBusiness = agentType === 'business';

  return (
    <form
      onSubmit={(e) => {
        e.preventDefault();
        form.handleSubmit();
      }}
      className="space-y-4"
    >
      {/* 1. SELECT FIELD WITH FIELD COMPONENT */}
      <form.Field name="agentType">
        {(field) => (
          <Field>
            <FieldLabel>Agent Type</FieldLabel>
            <Select
              value={field.state.value}
              onValueChange={(value) => {
                const typedValue = value as 'individual' | 'business';
                field.handleChange(typedValue);
                setAgentType(typedValue);
              }}
            >
              <SelectTrigger>
                <SelectValue placeholder="Select agent type" />
              </SelectTrigger>
              <SelectContent>
                <SelectItem value="individual">Individual</SelectItem>
                <SelectItem value="business">Business/Entity</SelectItem>
              </SelectContent>
            </Select>
          </Field>
        )}
      </form.Field>

      {/* 2. SIMPLE INPUT WITH FIELD COMPONENT */}
      <form.Field name="agentName">
        {(field) => (
          <Field>
            <FieldLabel>
              {isBusiness ? 'Business/Entity Name' : 'Agent Full Name'}
            </FieldLabel>
            <Input
              id={agentNameId}
              value={field.state.value}
              onChange={(e) => field.handleChange(e.target.value)}
              placeholder={
                isBusiness ? 'Enter business or entity name' : 'Enter agent full name'
              }
              required
            />
          </Field>
        )}
      </form.Field>

      {/* 3. CONDITIONAL FIELDS */}
      {isBusiness && (
        <>
          <form.Field name="agentTitle">
            {(field) => (
              <Field>
                <FieldLabel>Name and Title of Signatory</FieldLabel>
                <Input
                  id={agentTitleId}
                  value={field.state.value}
                  onChange={(e) => field.handleChange(e.target.value)}
                  placeholder="e.g., John Doe, CEO"
                  required={isBusiness}
                />
              </Field>
            )}
          </form.Field>
        </>
      )}

      {/* 4. INPUT GROUP WITH ICON (LEFT POSITION) */}
      <form.Field name="agentMailingAddress">
        {(field) => (
          <Field>
            <FieldLabel>Mailing Address</FieldLabel>
            <InputGroup>
              <InputGroupIcon>
                <MapPin />
              </InputGroupIcon>
              <InputGroupInput
                id={agentMailingAddressId}
                value={field.state.value}
                onChange={(e) => field.handleChange(e.target.value)}
                placeholder="Street address"
                required
              />
            </InputGroup>
          </Field>
        )}
      </form.Field>

      {/* 5. GRID LAYOUT WITH FIELD COMPONENTS */}
      <div className="grid grid-cols-6 gap-4">
        <form.Field name="agentMailingCity">
          {(field) => (
            <Field orientation="vertical" className="col-span-3">
              <Input
                id={agentMailingCityId}
                value={field.state.value}
                onChange={(e) => field.handleChange(e.target.value)}
                placeholder="City"
                required
              />
            </Field>
          )}
        </form.Field>

        <form.Field name="agentMailingState">
          {(field) => (
            <Field orientation="vertical" className="col-span-1">
              <Input
                id={agentMailingStateId}
                value={field.state.value}
                onChange={(e) => field.handleChange(e.target.value)}
                placeholder="CA"
                maxLength={2}
                required
              />
            </Field>
          )}
        </form.Field>

        <form.Field name="agentMailingZipCode">
          {(field) => (
            <Field orientation="vertical" className="col-span-2">
              <Input
                id={agentMailingZipCodeId}
                value={field.state.value}
                onChange={(e) => field.handleChange(e.target.value)}
                placeholder="ZIP Code"
                required
              />
            </Field>
          )}
        </form.Field>
      </div>

      {/* 6. INPUT GROUP WITH ICON AND INTERACTIVE ADDON */}
      <form.Field name="agentPhoneNumber">
        {(field) => (
          <Field>
            <FieldLabel>Phone Number</FieldLabel>
            <InputGroup>
              <InputGroupIcon>
                <Phone />
              </InputGroupIcon>
              <InputGroupInput
                id={agentPhoneNumberId}
                type="tel"
                value={field.state.value}
                onChange={(e) => {
                  const formatted = phoneFormatter.handleChange(e.target.value);
                  field.handleChange(formatted);
                }}
                placeholder="(123) 456-7890"
                required
              />
              {field.state.value && (
                <InputGroupAddon>
                  <Button
                    type="button"
                    variant="ghost"
                    size="sm"
                    className="h-6 w-6 p-0 hover:bg-transparent"
                    onClick={() => field.handleChange('')}
                    aria-label="Clear phone number"
                  >
                    <X className="h-3 w-3" />
                  </Button>
                </InputGroupAddon>
              )}
            </InputGroup>
          </Field>
        )}
      </form.Field>

      {/* ERROR HANDLING */}
      {errorMessage && (
        <Alert variant="destructive">
          <AlertDescription>{errorMessage}</AlertDescription>
        </Alert>
      )}

      {/* SUBMIT BUTTON */}
      <div className="flex justify-end gap-2 pt-4 border-t">
        <Button
          type="button"
          variant="outline"
          onClick={() => {/* handle cancel */}}
          disabled={mutation.isPending}
        >
          Cancel
        </Button>
        <Button type="submit" disabled={mutation.isPending}>
          {mutation.isPending ? (
            <>
              <Spinner className="mr-2" />
              Submitting...
            </>
          ) : (
            'Submit'
          )}
        </Button>
      </div>
    </form>
  );
}

Read the full file on GitHub · 440 lines

Changes

What this file has done since we first saw it

Hashed on every crawl. A supply-chain change to an agent config is a question of when, not whether, so the history is kept rather than the latest state alone.

  1. 3d ago First seen · 440 lines · 0 tokens per session scan A 529f1b0c7462

Subscribe to this mod's changes

shadcn-form-guide is a command published in the GitHub repository dyeoman2/tanstack-start-template (26 stars, last pushed 4mo ago), licensed MIT. It costs nothing until one of its globs matches a file; then it loads 2,781 tokens. A static security scan graded it A with 0 findings. No closer match exists in the catalogue, so it is treated as the original; first seen 2026-09-01.