form
Skill0 starsChanged 44 days agoApache-2.0View on GitHub
Form patterns for webapp data entry: drawer-based add/edit forms built on the pre-built `DrawerRoot`, and inline forms for one-to-three field cases like login, settings and quick filters. Covers validation, error and loading states, and field layout. Use when building a form, settings panel, or CRUD flow.
4 other projects use a version of this.
Adopted by
Most recently taken up by
- MohammadShehadeh/agent-skillstheir own version, first seen Nov 1, 2026
- lilly-builds/ads-cookingtheir own version, first seen Oct 20, 2026
- IamK77/Skilltheir own version, first seen Oct 12, 2026
- gabriellst/codmtheir own version, first seen Aug 26, 2026
Other versions4 projects use a version of this.
Word for word · 0
None found.
Edited · 0
None found.
Wrote their own version · 4
- gabriellst/codm★ 4same name
- IamK77/Skill★ 2same name
- MohammadShehadeh/agent-skills★ 2same name
- lilly-builds/ads-cooking★ 0same name
How matches are found. Word for word: the exact same text. Edited: shares passages with this file (“99% same” means 99% of its five-word phrases also appear here; 10% or more counts). Their own version: similar meaning, or, weakest, the same name. We can’t tell which project had it first. Projects are counted among the 200 most-starred candidates, and mentions are links to the repository we found, up to 50 per site, so both counts are a floor.
In this repository15 other files it gives its agents.
- authSkill
- cart-checkoutSkill
- chartSkill
- commerce-adminSkill
- commerce-coreSkill
- dashboard-motionSkill
- drawerSkill
- kanbanSkill
- mediaSkill
- metric-cardSkill
- modalSkill
- product-catalogSkill
- product-detailSkill
- sidenavSkill
- tableSkill
The file
We split this skill into the rules it gives an agent
3 rules. 0 are common, 2 are distinctive, and 1 is very specific to this project.
0
Common
Most of the 4 edited versions we read keep these.
2
Distinctive
This author's own choices. Worth a look.
1
Very specific
Tied to their project's names, files or tools. Probably skip.
- Common
- Distinctive
- Very specific
- Distinctive: line 40
Every form field MUST handle:
- Very specific: line 53
Use DrawerRoot.vue from ~/components/shared/DrawerRoot.vue.
- Distinctive: line 150
Use for: login, registration, settings panels, search filters, single-field edits.
“Common” is measured: kept by at least 6 in 10 of the 4 edited versions of this file we could read. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.
Files
Form Skill
When to use
Pages with input forms — login, registration, settings, data entry, filters.
Two form patterns
Choose based on context:
| Pattern | Use for | Don't use for |
| Drawer (recommended) | Add/edit records, multi-field forms, CRUD, forms that benefit from focused attention without leaving the page | Login, single-field edits, quick filters |
| Inline | Login, settings panels, quick filters, single-field edits, anything with 1-3 fields | Complex CRUD with 5+ fields requiring full attention |
A drawer uses the pre-generated DrawerRoot.vue from ~/components/shared/DrawerRoot.vue. Never reconstruct the drawer shell manually — always compose it.
Required imports
- Radix-Vue:
Input,Select,Checkbox,Switch,Textarea— form controls - Shared component:
DrawerRootfrom~/components/shared/DrawerRoot.vue - Icons:
AlertCircle,Check,Eye,EyeOff,Loader2,X
States (non-negotiable)
Every form field MUST handle:
- Default — empty or pre-filled with current value
- Focused — ring color via
focus-visible:ring-ring - Error — red border + error message below (
text-destructive text-sm) - Disabled —
opacity-50 cursor-not-allowed(for loading/submitted states)
Buttons MUST handle:
- Default — interactive state
- Loading — spinner icon +
pointer-events-none opacity-70 - Disabled — form invalid or already submitted
Drawer form pattern (recommended for add/edit)
Use DrawerRoot.vue from ~/components/shared/DrawerRoot.vue. It handles position, size, transitions, backdrop, escape key, and click-outside. You only provide the form content.
<script setup lang="ts">
import DrawerRoot from '~/components/shared/DrawerRoot.vue'
import { AlertCircle, Loader2 } from 'lucide-vue-next'
const formOpen = ref(false)
const saving = ref(false)
const error = ref('')
const formData = ref({ name: '', email: '', role: '' })
async function handleSubmit() {
saving.value = true
error.value = ''
try {
// submit logic
formOpen.value = false
} catch (e: any) {
error.value = e.message
} finally {
saving.value = false
}
}
</script>
<template>
<button
class="px-4 py-2 rounded-lg bg-primary text-primary-foreground font-medium text-sm
hover:bg-primary/90 hover:scale-[1.02] active:scale-[0.98] transition-all duration-150"
@click="formOpen = true"
>
Add Record
</button>
<DrawerRoot :open="formOpen" title="Add Record" size="md" @close="formOpen = false">
<!-- Error banner at top of body -->
<div v-if="error" class="flex items-center gap-2 rounded-lg bg-destructive/10 p-3 text-sm text-destructive mb-4">
<AlertCircle class="h-4 w-4 shrink-0" />
<span>{{ error }}</span>
</div>
<!-- Form fields with space-y between groups -->
<form @submit.prevent="handleSubmit" class="space-y-4">
<div v-for="field in formFields" :key="field.key" class="space-y-1.5">
<label class="text-sm font-medium text-foreground">{{ field.label }}</label>
<input
v-model="formData[field.key as keyof typeof formData]"
:type="field.type || 'text'"
:class="[
'w-full px-3 py-2 rounded-lg border border-input bg-background text-foreground text-sm',
'focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring',
'disabled:opacity-50 disabled:cursor-not-allowed transition-colors',
field.error ? 'border-destructive' : '',
]"
:disabled="saving"
:placeholder="field.placeholder"
/>
<p v-if="field.error" class="flex items-center gap-1 text-destructive text-sm">
<AlertCircle class="h-3 w-3" />
{{ field.error }}
</p>
</div>
</form>
<!-- Footer: Cancel + Save -->
<template #footer>
<button
class="inline-flex h-9 items-center rounded-lg px-4 text-sm font-medium text-foreground hover:bg-muted transition-all duration-150 disabled:opacity-50 disabled:cursor-not-allowed"
:disabled="saving"
@click="formOpen = false"
>
Cancel
</button>
<button
class="inline-flex h-9 items-center gap-2 rounded-lg bg-primary px-4 text-sm font-medium text-primary-foreground shadow-lg shadow-primary/20 hover:bg-primary/90 hover:shadow-xl hover:shadow-primary/30 hover:scale-[1.02] active:scale-[0.98] transition-all duration-150 disabled:opacity-50 disabled:cursor-not-allowed disabled:hover:scale-100"
:disabled="saving"
@click="handleSubmit"
>
<Loader2 v-if="saving" class="h-4 w-4 animate-spin" />
{{ saving ? 'Saving...' : 'Save' }}
</button>
</template>
</DrawerRoot>
</template>Key improvements over manual drawer:
- Spring easing (cubic-bezier(0.34,1.56,0.64,1)) is built into DrawerRoot — no manual easing needed
- Exit animation is shorter (200ms) and uses ease-in — DrawerRoot handles this
- Color-matched shadows:
shadow-primary/20on the save button - Scale feedback:
hover:scale-[1.02] active:scale-[0.98]on both trigger and save - Error banner uses
bg-destructive/10notbg-muted
Inline form pattern (for simple cases)
Use for: login, registration, settings panels, search filters, single-field edits.
<script setup lang="ts">
const email = ref('')
const error = ref('')
const loading = ref(false)
async function handleSubmit() {
loading.value = true
try { /* submit logic */ }
catch (e: any) { error.value = e.message }
finally { loading.value = false }
}
</script>
<template>
<form @submit.prevent="handleSubmit" class="space-y-4 max-w-md">
<div class="space-y-1.5">
<label class="text-sm font-medium text-foreground">Email</label>
<input
v-model="email"
type="email"
class="w-full px-3 py-2 rounded-lg border border-input bg-background text-foreground text-sm
focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring
disabled:opacity-50 disabled:cursor-not-allowed transition-colors"
:class="{ 'border-destructive': error }"
:disabled="loading"
/>
<p v-if="error" class="flex items-center gap-1 text-destructive text-sm">
<AlertCircle class="h-3 w-3" />
{{ error }}
</p>
</div>
<button
type="submit"
class="w-full inline-flex items-center justify-center gap-2 px-4 py-2 rounded-lg
bg-primary text-primary-foreground font-medium text-sm
shadow-lg shadow-primary/20 hover:bg-primary/90
hover:shadow-xl hover:shadow-primary/30 hover:scale-[1.02] active:scale-[0.98]
transition-all duration-150 disabled:opacity-50 disabled:cursor-not-allowed disabled:hover:scale-100"
:disabled="loading"
>
<Loader2 v-if="loading" class="h-4 w-4 animate-spin" />
{{ loading ? 'Submitting...' : 'Submit' }}
</button>
</form>
</template>CSS variable usage
- Labels:
text-foreground font-medium - Inputs:
border-input bg-background text-foreground, focus:ring-ring - Errors:
text-destructive, input border:border-destructive - Buttons default:
bg-primary text-primary-foreground shadow-primary/20 - Buttons disabled:
opacity-50 cursor-not-allowed - Drawer uses
DrawerRoot.vue— refer to its documentation for drawer shell CSS - Error banner:
bg-destructive/10(NOTbg-muted)
Non-negotiables
- Drawers MUST use
DrawerRoot.vuefrom~/components/shared/DrawerRoot.vue— never reconstruct the drawer shell manually - Save button shows
Loader2spinner during submission, all fields disabled - Overlay click closes drawer (DrawerRoot handles this)
- Same field state pattern applies: default, focused, error, disabled
- Same button state pattern applies: default, loading, disabled
- Spring easing (cubic-bezier(0.34,1.56,0.64,1)) on enter, shorter ease-in on exit — DrawerRoot handles this automatically
- Error banner: always use
bg-destructive/10 text-destructive— neverbg-muted - Submit button: MUST have
hover:scale-[1.02] active:scale-[0.98]for tactile feedback - Submit button: MUST have color-matched shadow (
shadow-primary/20orshadow-destructive/20) - For the inline pattern, never use side effects — keep it form-in-page
Colour & polish
- Focus-visible ring from
ring; inline error text indestructive.
Does it work?
Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.
No reviews yet
People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.
- It worked
- Partly
- It didn't
Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.
Your trail
Files you open will collect here, so you can find them again.
Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.
Now do this for your own.
Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.

