Form skill
The bestax-form
skill teaches an agent to build forms with the bestax form components. There is no form
library — state is plain React, and errors surface via color="danger" + message +
messageColor.
Install
npx skills add https://github.com/allxsmith/bestax --skill bestax-form
Signup & validation
Prompt
Build a signup form (name, email, country) that validates on submit and shows
inline errors — no form library — following the bestax form skill.
Live form
Click Create account with empty fields to see the validation.
function example() { const [name, setName] = useState(''); const [email, setEmail] = useState(''); const [country, setCountry] = useState(''); const [submitted, setSubmitted] = useState(false); const errors = { name: !name.trim() ? 'Name is required.' : undefined, email: !/^[^@\s]+@[^@\s]+\.[^@\s]+$/.test(email) ? 'Enter a valid email.' : undefined, country: !country ? 'Pick a country.' : undefined, }; const show = k => (submitted ? errors[k] : undefined); return ( <form onSubmit={e => { e.preventDefault(); setSubmitted(true); }} style={{ maxWidth: '28rem' }} > <Input label="Name" value={name} onChange={e => setName(e.target.value)} color={show('name') ? 'danger' : undefined} message={show('name')} messageColor="danger" iconLeftName="user" /> <Input label="Email" type="email" value={email} onChange={e => setEmail(e.target.value)} color={show('email') ? 'danger' : undefined} message={show('email')} messageColor="danger" iconLeftName="envelope" /> <Select label="Country" value={country} onChange={e => setCountry(e.target.value)} color={show('country') ? 'danger' : undefined} message={show('country')} messageColor="danger" > <option value="">Select…</option> <option value="us">United States</option> <option value="ca">Canada</option> <option value="uk">United Kingdom</option> </Select> <Button color="primary" type="submit"> Create account </Button> </form> ); }
Validation pattern
Own the state, compute errors, and reflect them on each field with color="danger" (colors the
input), message (the help text), and messageColor="danger" (colors the help text).
Account settings
Prompt
Build an account settings form with a bio, a timezone select, an email-digest
radio group, interest checkboxes, and toggle switches — following the bestax
form skill.
Live form
function example() { const [bio, setBio] = useState(''); const [timezone, setTimezone] = useState('utc'); const [digest, setDigest] = useState('weekly'); const [interests, setInterests] = useState(['react']); const [darkMode, setDarkMode] = useState(true); const [twoFactor, setTwoFactor] = useState(false); return ( <form style={{ maxWidth: '32rem' }}> <TextArea label="Bio" value={bio} onChange={e => setBio(e.target.value)} rows={3} placeholder="Tell us about yourself" /> <Select label="Timezone" value={timezone} onChange={e => setTimezone(e.target.value)} > <option value="utc">UTC</option> <option value="est">Eastern</option> <option value="pst">Pacific</option> </Select> <Field label="Email digest"> <Radios name="digest" value={digest} onChange={setDigest}> <Radio value="daily">Daily</Radio> <Radio value="weekly">Weekly</Radio> <Radio value="never">Never</Radio> </Radios> </Field> <Field label="Interests"> <Checkboxes name="interests" value={interests} onChange={setInterests}> <Checkbox value="react">React</Checkbox> <Checkbox value="bulma">Bulma</Checkbox> <Checkbox value="design">Design</Checkbox> </Checkboxes> </Field> <Field> <Switch checked={darkMode} onChange={e => setDarkMode(e.target.checked)} color="primary" > Dark mode </Switch> </Field> <Field> <Switch checked={twoFactor} onChange={e => setTwoFactor(e.target.checked)} color="success" > Two-factor authentication </Switch> </Field> <Button color="primary">Save settings</Button> </form> ); }
Grouped controls
Checkboxes manages an array value; Radios manages a single value. Each child carries a
value, and the group reports the selection through onChange.
Advanced inputs
Prompt
Build a freelancer profile form using the advanced inputs: autocomplete, slider,
number input, star rating, and tag input — following the bestax form skill.
Live form
function example() { const [skill, setSkill] = useState(''); const [experience, setExperience] = useState(5); const [rateUsd, setRateUsd] = useState(75); const [quality, setQuality] = useState(4); const [tags, setTags] = useState(['typescript']); return ( <div style={{ maxWidth: '32rem' }}> <Autocomplete label="Primary skill" value={skill} onInput={setSkill} onSelect={item => setSkill(typeof item === 'string' ? item : (item?.value ?? '')) } data={['React', 'Vue', 'Svelte', 'Angular']} openOnFocus /> <Slider label="Years of experience" value={experience} onChange={setExperience} min={0} max={20} tooltip="auto" /> <Numberinput label="Hourly rate (USD)" value={rateUsd} onChange={setRateUsd} min={10} max={500} step={5} /> <Rate label="Self-rated quality" value={quality} onChange={setQuality} max={5} /> <Taginput label="Tech stack" value={tags} onChange={next => setTags(next.map(t => (typeof t === 'string' ? t : t.value))) } data={['typescript', 'node', 'graphql', 'css']} /> </div> ); }
Wiring notes
Autocomplete reports typing via onInput and a chosen suggestion via onSelect. Taginput's
onChange yields tag objects-or-strings, so map them when you store plain strings. A dual-thumb
Slider needs the range prop.
Files & scheduling
Prompt
Build an onboarding form with a file upload plus date, time, and date-time
pickers — following the bestax form skill.
Live form
function example() { const [date, setDate] = useState(null); const [time, setTime] = useState(null); const [appt, setAppt] = useState(null); return ( <div style={{ maxWidth: '28rem' }}> <Field label="Profile photo"> <File hasName buttonLabel="Choose a file…" /> </Field> <DateInput label="Date of birth" value={date} onChange={setDate} /> <TimeInput label="Preferred contact time" value={time} onChange={setTime} /> <DateTimeInput label="Appointment" value={appt} onChange={setAppt} /> </div> ); }
Controlled date & time
The date/time inputs are controlled with a Date | null value and an onChange(date) handler,
just like the text inputs — only the widget differs.