Saleor App UI
saleor/apps
Styling and layout guide for Saleor Apps using modern macaw-ui and @saleor/apps-ui-next.
Correct React Hook Form usage anywhere in the monorepo — data flow, subscriptions, reset, dirty state, number inputs, and controlled-input rules.
$ npx skills add supabase/supabase --skill react-hook-form -a claude-codeProject install by default; add -g for ~/.claude/skills/.
$ gh skill install supabase/supabase react-hook-form --agent claude-codeProject scope by default; add --scope user for a personal install. Needs GitHub CLI 2.90.0 or later (public preview).
$ git clone --depth 1 https://github.com/supabase/supabase.git skills-src && mkdir -p .claude/skills && cp -r skills-src/.agents/skills/react-hook-form .claude/skills/react-hook-form && rm -rf skills-srcUse ~/.claude/skills/ instead of .claude/skills for a personal install. The folder must contain SKILL.md.
Claude Code skills documentation · loads skills from .claude/skills/
Install the "react-hook-form" agent skill from https://github.com/supabase/supabase/tree/master/.agents/skills/react-hook-form into .claude/skills/react-hook-form/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "react-hook-form", then confirm the skill loads.Claude Code copies the folder itself, the same result as the manual copy. Check what it changed before you commit it.
$skill-installer install https://github.com/supabase/supabase/tree/master/.agents/skills/react-hook-formType this inside Codex. $skill-installer <name> installs a curated skill from openai/skills. The installer writes to $CODEX_HOME/skills (default ~/.codex/skills). Restart Codex if the skill does not show up.
$ npx skills add supabase/supabase --skill react-hook-form -a codexProject install goes to .agents/skills/; add -g for ~/.codex/skills/.
$ gh skill install supabase/supabase react-hook-form --agent codexProject scope by default (.agents/skills/); add --scope user for a personal install.
$ git clone --depth 1 https://github.com/supabase/supabase.git skills-src && mkdir -p .agents/skills && cp -r skills-src/.agents/skills/react-hook-form .agents/skills/react-hook-form && rm -rf skills-srcUse ~/.agents/skills/ instead of .agents/skills for a personal install.
Codex skills documentation · loads skills from .agents/skills/
Install the "react-hook-form" agent skill from https://github.com/supabase/supabase/tree/master/.agents/skills/react-hook-form into .agents/skills/react-hook-form/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "react-hook-form", then confirm the skill loads.Codex copies the folder itself, the same result as the manual copy. Check what it changed before you commit it.
$ npx skills add supabase/supabase --skill react-hook-form -a cursorProject install goes to .agents/skills/; add -g for ~/.cursor/skills/.
$ gh skill install supabase/supabase react-hook-form --agent cursorProject scope by default (.agents/skills/); add --scope user for a personal install.
$ git clone --depth 1 https://github.com/supabase/supabase.git skills-src && mkdir -p .cursor/skills && cp -r skills-src/.agents/skills/react-hook-form .cursor/skills/react-hook-form && rm -rf skills-srcUse ~/.cursor/skills/ instead of .cursor/skills for a personal install.
Cursor skills documentation · loads skills from .cursor/skills/, .agents/skills/, .claude/skills/, .codex/skills/
Install the "react-hook-form" agent skill from https://github.com/supabase/supabase/tree/master/.agents/skills/react-hook-form into .cursor/skills/react-hook-form/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "react-hook-form", then confirm the skill loads.Cursor copies the folder itself, the same result as the manual copy. Check what it changed before you commit it.
$ gemini skills install https://github.com/supabase/supabase.git --path .agents/skills/react-hook-form--scope user (default) or --scope workspace; --path is the subfolder of the repo that holds the skill; --consent skips the security confirmation prompt.
$ npx skills add supabase/supabase --skill react-hook-form -a gemini-cliProject install goes to .agents/skills/; add -g for ~/.gemini/skills/.
$ gh skill install supabase/supabase react-hook-form --agent gemini-cliProject scope by default (.agents/skills/); add --scope user for a personal install.
$ git clone --depth 1 https://github.com/supabase/supabase.git skills-src && mkdir -p .gemini/skills && cp -r skills-src/.agents/skills/react-hook-form .gemini/skills/react-hook-form && rm -rf skills-srcUse ~/.gemini/skills/ instead of .gemini/skills for a personal install, then run /skills reload.
Gemini CLI skills documentation · loads skills from .gemini/skills/, .agents/skills/
Install the "react-hook-form" agent skill from https://github.com/supabase/supabase/tree/master/.agents/skills/react-hook-form into .gemini/skills/react-hook-form/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "react-hook-form", then confirm the skill loads.Gemini CLI copies the folder itself, the same result as the manual copy. Check what it changed before you commit it.
$ gh skill install supabase/supabase react-hook-formInstalls for Copilot at project scope by default; add --scope user for a personal install. Preview a skill first with gh skill preview. Needs GitHub CLI 2.90.0 or later (public preview).
$ npx skills add supabase/supabase --skill react-hook-form -a github-copilotProject install goes to .agents/skills/; add -g for ~/.copilot/skills/.
$ git clone --depth 1 https://github.com/supabase/supabase.git skills-src && mkdir -p .github/skills && cp -r skills-src/.agents/skills/react-hook-form .github/skills/react-hook-form && rm -rf skills-srcUse ~/.copilot/skills/ instead of .github/skills for a personal install. Commit .github/skills so cloud agent and code review can use it.
GitHub Copilot skills documentation · loads skills from .github/skills/, .claude/skills/, .agents/skills/
Install the "react-hook-form" agent skill from https://github.com/supabase/supabase/tree/master/.agents/skills/react-hook-form into .github/skills/react-hook-form/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "react-hook-form", then confirm the skill loads.GitHub Copilot copies the folder itself, the same result as the manual copy. Check what it changed before you commit it.
$ npx skills add supabase/supabase --skill react-hook-form -a opencodeOpenCode documents no install command of its own. Project install goes to .agents/skills/; add -g for ~/.config/opencode/skills/.
$ gh skill install supabase/supabase react-hook-form --agent opencodeProject scope by default (.agents/skills/); add --scope user for a personal install.
$ git clone --depth 1 https://github.com/supabase/supabase.git skills-src && mkdir -p .opencode/skills && cp -r skills-src/.agents/skills/react-hook-form .opencode/skills/react-hook-form && rm -rf skills-srcUse ~/.config/opencode/skills/ instead of .opencode/skills for a personal install.
OpenCode skills documentation · loads skills from .opencode/skills/, .claude/skills/, .agents/skills/
Install the "react-hook-form" agent skill from https://github.com/supabase/supabase/tree/master/.agents/skills/react-hook-form into .opencode/skills/react-hook-form/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "react-hook-form", then confirm the skill loads.OpenCode copies the folder itself, the same result as the manual copy. Check what it changed before you commit it.
react-hook-formCorrect React Hook Form usage anywhere in the monorepo — data flow, subscriptions, reset, dirty state, number inputs, and controlled-input rules.
React Hook Form is an agent skill from supabase/supabase, published by the product's own GitHub organization. Correct React Hook Form usage anywhere in the monorepo — data flow, subscriptions, reset, dirty state, number inputs, and controlled-input rules. Load this BEFORE writing or modifying ANY form code, adding a field to an existing form, touching watch/useWatch/formState/getValues/setValue/reset, wiring a form into a dialog or sheet, or building a submit/cancel footer — even when the change looks trivial. The codebase contains widespread RHF anti-patterns; without this skill you will copy them. For form layout and…
Its SKILL.md is about 4k tokens, which your agent loads only when the skill is triggered. It is a single SKILL.md file with no bundled scripts.
It sits in Frontend & Design, covering React components and Monorepo tooling. The repository describes itself as: The Postgres development platform. Supabase gives you a dedicated Postgres database to build your web, mobile, and AI applications. The licence is Apache-2.0.
2 steps, taken from the first numbered list in SKILL.md.
Read from SKILL.md and the folder at commit 26c838a. It shows what the files ask for, not the result of running them.
Pre-approves nothing: there is no allowed-tools line, so your agent's usual permission prompts apply.
From allowed-tools in the SKILL.md frontmatter.
No scripts in the folder and no shell commands in SKILL.md (its code samples are typescript).
From the folder's file list and the shell code blocks in SKILL.md.
No URLs in SKILL.md.
From URLs in SKILL.md, links to its own repository left out.
Names no API keys, tokens, secrets or passwords.
From names ending in _API_KEY, _TOKEN, _SECRET, _KEY or _PASSWORD in SKILL.md.
React Hook Form loads about 4k tokens when it runs. Until then it costs about 147 tokens; SKILL.md has 1,639 words of instructions outside code blocks.
Estimates: characters ÷ 4, the usual rule of thumb; real counts depend on the model's tokenizer. Scripts and assets cost tokens only if the agent reads them.
The automated check found no risky patterns in SKILL.md.
Automated static check — not a guarantee. Review scripts before installing. It scans the text of SKILL.md for risky patterns (piping downloads into a shell, reading credential files, hidden Unicode, destructive commands); files beside SKILL.md are not scanned.
The full file from supabase/supabase at commit 26c838a, republished under its Apache-2.0 licence (© supabase). 1,639 words, ~3,962 tokens.
.claude/skills/react-hook-form/SKILL.md (or your agent's skills folder).How to write forms that stay correct as they grow. The existing codebase is not
a safe reference: form.watch() off prop-drilled form objects, subscription-only
watches, unguarded valueAsNumber, and ?? undefined controlled values are all
common in older code and all wrong. Follow this skill, not the neighboring file.
Policy — fix what you touch. New code must follow these rules. When you modify
existing form code, upgrade the specific fields/hooks/components you're editing to
match (e.g. a component you touch that calls form.watch gets converted to
useWatch). Leave untouched code alone, but tell the user about anti-patterns you
noticed and didn't fix. Never add new violations: react-hook-form/no-use-watch
is ratcheted in Studio CI — any increase in the warning count fails the build.
RHF is uncontrolled at heart. Values live in refs; nothing re-renders unless a subscription says so. Every read API is a subscription decision:
| API | Subscribes | Re-renders | Use for |
|---|---|---|---|
useWatch({ control, name }) | yes | only the calling component | reactive value reads, anywhere |
useFormState({ control }) | yes | only the calling component | isDirty/errors/etc. outside the form owner |
formState (destructured) | yes | the useForm owner | form state in the owner component only |
form.watch(name) | yes | the entire form tree | avoid — lint-flagged, see below |
getValues() | no | never | event handlers and onSubmit only |
subscribe() | callback | none | side effects outside render |
Two facts explain most of the bugs we've shipped:
form.watch() and form.formState hoist their subscription to the useForm
owner, no matter which component calls them. A child that reads
form.watch('x') off a prop works today only because the whole tree re-renders
on every change — it silently goes stale the moment anyone adds React.memo
between owner and child, and until then it re-renders every sibling on every
keystroke. A no-arg form.watch() sets watchAll and re-renders the tree on
every field change for the life of the form.formState is a Proxy — reading a property is what arms the subscription.
Destructure it (const { isDirty } = form.formState), never pass the object
around or read it conditionally (a && formState.isValid may never subscribe).
Enforced by react-hook-form/destructuring-formstate (error).useForm: destructure formState; prefer
useWatch over form.watch even here (the no-use-watch rule flags every
watch, and useWatch scopes the re-render if the JSX is later extracted).control (not the whole
form) and use useWatch({ control, name }) / useFormState({ control }).
Inside <Form {...form}> (which is FormProvider), useFormContext() +
useWatch({ name }) also works and avoids prop-drilling entirely.getValues() — the watch list and the read list will
drift apart (it has already happened; fields silently lost reactivity). The
value you render must be the value you subscribed to.useWatch('x') on one line and
getValues('x') a few lines later lets the two disagree within a single render.useWatch({ control }) with no name re-renders on
every keystroke in every field. Subscribe to the specific names you use.watch(callback) is deprecated — use subscribe() for render-free listeners,
and always return its cleanup from useEffect.// ❌ common in the codebase — all three subscriptions hoist to the form owner
function Fields({ form }: { form: UseFormReturn<FormValues> }) {
form.watch(['storageType', 'totalSize']) // return value discarded
const { errors } = form.formState // prop-form formState
const size = form.getValues('totalSize') // non-reactive read in render
...
}
// ✅ child subscribes for itself and consumes what it watches
function Fields({ control }: { control: Control<FormValues> }) {
const [storageType, totalSize] = useWatch({ control, name: ['storageType', 'totalSize'] })
const { errors } = useFormState({ control })
...
}zod schema → z.infer type → useForm with zodResolver and complete
defaultValues → <Form {...form}> → FormField render-prop per field →
FormItemLayout → FormControl → primitive from ui. Layout/container choices
(Card vs Sheet, layout= variants) are covered by the studio-ui-patterns skill
and the demos in apps/design-system/registry/default/example/
(form-patterns-pagelayout.tsx, form-patterns-sidepanel.tsx) — check them
before inventing structure.
// Module level — static references, not recreated on every render
const FORM_ID = 'pool-config-form'
const FormSchema = z.object({
name: z.string().min(1, 'Name is required'),
maxConnections: z
.union([z.literal(''), z.coerce.number().gte(1, 'Must be at least 1')])
.refine((v) => v !== '', 'Max connections is required'),
})
type FormValues = z.infer<typeof FormSchema>
const defaultValues: FormValues = { name: '', maxConnections: '' }
// Inside the component
const form = useForm<FormValues>({
resolver: zodResolver(FormSchema),
defaultValues,
})
<Form {...form}>
<form id={FORM_ID} onSubmit={form.handleSubmit(onSubmit)}>
<FormField
control={form.control}
name="name"
render={({ field }) => (
<FormItemLayout layout="horizontal" label="Name">
<FormControl>
<Input {...field} />
</FormControl>
</FormItemLayout>
)}
/>
</form>
</Form>Define the schema, type, static defaultValues, and the form's id at module
level, outside the component. Rebuilding them per render is wasted work and
unstable references — RHF reads defaultValues only on the first render, but
anything else comparing against these objects sees a fresh identity each time.
When they genuinely depend on runtime data, build the schema with useMemo and
feed server-driven defaults through the values option (next section) instead
of hoisting.
Submit buttons living outside the <form> (sheet/dialog footers) use the same
module-level FORM_ID via form={FORM_ID} on the button. A module-level id is
only safe for singleton forms — if the component can mount more than once at a
time, duplicate ids make external buttons submit the first matching form, so
mint a per-instance id with useId() and share it between the <form> and its
buttons.
defaultValues object — every field, no undefined.
isDirty, dirtyFields, and Cancel-reset all compare against it; a missing or
undefined default breaks all three, and undefined also makes React treat the
input as uncontrolled (see below).values option, not a hand-rolled
effect. values reacts to the query resolving and resets the form for you;
computing defaultValues from a query that may not have loaded freezes whatever
happened to be in cache at mount. Add
resetOptions: { keepDirtyValues: true } when a background refetch must not
clobber the user's in-progress edits. keepDirtyValues preserves whatever is
in formState.dirtyFields; typed edits and setValue(…, { shouldDirty: true })
populate it regardless of subscriptions, but useFieldArray operations
(append/remove/move) only mark fields dirty while dirtyFields or isDirty
is subscribed — so a form that combines keepDirtyValues with a field array
must read one of them in the owner, or the next refetch will discard array
edits. (Good examples:
components/interfaces/Settings/Database/ConnectionLogging.tsx,
components/interfaces/Storage/FilesBuckets/EditBucketModal.tsx.)onSuccess so the
saved state becomes the new baseline (isDirty returns to false, Cancel now
reverts to the saved values). Prefer what the server actually persisted: if the
form uses values and the mutation invalidates the query, the refetch handles
this for you; if the mutation returns the updated resource, reset(response).
reset(submittedValues) is the fallback for APIs that store exactly what was
sent — if the server normalizes or fills values, it baselines the form to data
that was never saved. A bare reset() reverts to the previous defaults —
wrong after a save.form.reset(). This only visually restores fields whose
values round-trip through defined, controlled values — which is why the null
rules below matter.value flip to undefinedReact decides controlled vs uncontrolled per render from whether value is
defined. A field whose value can be undefined (or becomes undefined on reset)
flips modes: console warnings, and — worse — reset() stops clearing the visible
text because React abandoned the DOM value. value={field.value ?? undefined} is
a bug, not a fix.
'', never null/undefined.null from the API at the form boundary (growthPercent ?? ''
when building defaults) and convert back on submit ('' → null). Do not paper
over a null default with a placeholder that looks like a value: the user
sees "50", the form holds null, and every downstream comparison
(defaultValues.growthPercent !== watched → null !== 50) reports a permanent
phantom change while Cancel silently fails to reset the field.'' or a real option value; checkboxes/switches to
false.The blessed pattern keeps '' as the "empty" sentinel so the input stays
controlled, and lets zod coerce on validation (see maxConnections above):
z.union([z.literal(''), z.coerce.number()...]).refine((v) => v !== '', '…')
with a plain <Input {...field} type="number" />.
If you instead wire onChange through e.target.valueAsNumber (or
valueAsNumber: true), an empty or partially-typed input produces NaN, which
lands in form state and propagates into every calculation, price preview, and
value attribute downstream. Guard it with the same empty sentinel the
field's schema declares — with the ''-union schema above:
field.onChange(Number.isNaN(e.target.valueAsNumber) ? '' : e.target.valueAsNumber).
Never let NaN into form state.
A nullable API field (null = "unset", e.g. a platform default applies)
doesn't change the in-form sentinel — keep '' inside the form and convert at
the boundaries:
// inbound: null → '' when building defaults/values
values: { growthPercent: data.growth_percent ?? '' },
// schema: '' stays the in-form sentinel, zod coerces real input
growthPercent: z.union([z.literal(''), z.coerce.number().gte(10).lte(100)]),
// outbound: '' → null in onSubmit
mutate({ growth_percent: values.growthPercent === '' ? null : values.growthPercent })If null does end up in form state (some existing forms hold it), keep it out
of both the input and the coercion: render via value={field.value ?? ''}, and
don't pass the value through z.coerce.number() — Number(null) is 0, so a
nullable field fed into the coercing union silently validates empty as 0.
Either way it's one sentinel per field, used consistently across defaults,
schema, onChange, rendering, and the submit mapping.
isDirty; show Cancel only when dirty. In the owner, destructure
from form.formState; anywhere else, useFormState({ control }).useConfirmOnClose + DiscardChangesConfirmationDialog. Route Cancel,
Escape, and backdrop through the guard; call the raw onClose on successful
submit so you do not prompt after save. Details:
apps/design-system/content/docs/ui-patterns/modality.mdx (Dirty form
dismissal) and the studio-ui-patterns skill Sheets section.dirtyFields
from the same subscription instead of hand-comparing
defaultValues.x !== watchedX. RHF already does that comparison correctly;
hand-rolled versions break on the null-vs-placeholder mismatch and must be
kept in sync with the watch list by hand.setValue outside user input needs explicit flags:
setValue('x', v, { shouldDirty: true, shouldValidate: true }) — otherwise the
change is invisible to isDirty and validation.If a field must not be edited (plan tier, permissions, cooldown), disable the
field itself — a notice next to an editable input gates nothing. Wire the same
condition into both the notice and the control. Permission checks come from
useAsyncCheckPermissions; disabled buttons that need an explanation use
ButtonTooltip.
Caution: register/useController disabled: true removes the field's value
from submission data. For "visible but locked" fields whose value must survive
submit, use the input's own disabled/readOnly prop (as FormField +
primitive props do) rather than RHF-level disabling, or the form-level
disabled option to freeze everything during async work.
onSubmit receives validated, typed data — trust it; don't re-read via
getValues(). Mutations follow Studio conventions: onSuccess → toast.success
reset(values) (or query invalidation when using values:), onError →
toast.error; pass the mutation's isPending to the button's loading prop.
Default validation mode: 'onSubmit' is right for most forms — pick another mode
deliberately, not by copying.| Rule | Level | Meaning |
|---|---|---|
react-hook-form/destructuring-formstate | error | destructure formState, never hold the object |
react-hook-form/no-access-control | error | don't reach into control internals |
react-hook-form/no-nested-object-setvalue | error | setValue('a.b', v), not setValue('a', {b:v}) |
react-hook-form/no-use-watch | warn (ratcheted) | use useWatch, not watch |
© supabase, Apache-2.0. Rendered from Markdown: HTML in the file is shown as text, images as links, and headings moved down two levels. Raw file
Just SKILL.md in .agents/skills/react-hook-form of supabase/supabase.
Open the folder on GitHubat commit 26c838a
React Hook Form next to the 5 skills that share the most tags, products or categories with it. Stars are the repository's; “used in” counts other GitHub owners with a copy.
| Skill | Stars | Used in | Tokens | Auto-check | Licence | Repo updated |
|---|---|---|---|---|---|---|
| React Hook Form this skillsupabase/supabase | 111k | — | ~4k | Automated safety check: Pass | Apache-2.0 | |
| Saleor App UIsaleor/apps | 162 | — | ~5.8k | Automated safety check: Pass | Custom licence | |
| CSS ModulesOpentrons/opentrons | 521 | — | ~2.2k | Automated safety check: Pass | Apache-2.0 | |
| Opentrons TypescriptOpentrons/opentrons | 521 | — | ~3.7k | Automated safety check: Pass | Apache-2.0 | |
| React Router Developmentremix-run/react-router | 57k | 1 repos | ~1.5k | Automated safety check: Pass | MIT | |
| React UI State PatternsChrisWiles/claude-code-showcase | 6.1k | 8 repos | ~1.6k | Automated safety check: Pass | None |
saleor/apps
Styling and layout guide for Saleor Apps using modern macaw-ui and @saleor/apps-ui-next.
Opentrons/opentrons
CSS Modules conventions, Stylelint rules, design tokens (spacing, colors, typography, border-radius), and patterns for the Opentrons monorepo.
Opentrons/opentrons
TypeScript conventions, React patterns, testing, styling, and import rules for the Opentrons monorepo JS/TS packages.
remix-run/react-router
Guides work on React Router apps by first identifying whether the app uses Framework, Data or Declarative mode, then loading the matching reference and the installed package docs.
ChrisWiles/claude-code-showcase
Sets patterns for React interfaces: when to show loading spinners or skeletons, how to surface errors, how to disable buttons during async work and how to handle empty lists.
sanity-io/sanity
React and Next.js performance optimization guidelines from Vercel Engineering.
supabase/supabase
React composition patterns that scale. An agent skill from supabase/supabase.
supabase/supabase
Write, review, and migrate Supabase logs queries against the ClickHouse-backed logs table (the logs.all.otel analytics endpoint).
supabase/supabase
Review Supabase docs changes locally in your supabase/supabase checkout — either an open PR (triage, classify, verify) or your own branch before opening a PR (local self-review).
supabase/supabase
Vitest API and config reference (Jest-compatible) — mocking with vi., spies, fake timers, coverage configuration, fixtures, snapshots, and test filtering.
supabase/supabase
A skill your agent uses whenever code will build, return, fetch, or execute SQL that runs against a user's real Postgres database — even when the request reads like an ordinary feature or bug fix…
supabase/supabase
Write and run Playwright E2E tests for Supabase Studio (e2e/studio).
Categories
Correct React Hook Form usage anywhere in the monorepo — data flow, subscriptions, reset, dirty state, number inputs, and controlled-input rules. React Hook Form is an agent skill from supabase/supabase, published by the product's own GitHub organization. Correct React Hook Form usage anywhere in the monorepo — data flow, subscriptions, reset, dirty state, number inputs, and controlled-input rules.
React Hook Form fits situations like: tasks that involve React components; tasks that involve Monorepo tooling.
Run `npx skills add supabase/supabase --skill react-hook-form -a claude-code`. Or copy the skill folder (.agents/skills/react-hook-form in supabase/supabase) into .claude/skills/react-hook-form in your project. Claude Code loads it when a task matches its description.
Run `npx skills add supabase/supabase --skill react-hook-form -a codex`. Or copy the skill folder (.agents/skills/react-hook-form in supabase/supabase) into .agents/skills/react-hook-form in your project. Codex loads it when a task matches its description.
Cursor, Gemini CLI, GitHub Copilot and OpenCode also load SKILL.md folders. With the skills CLI, run `npx skills add supabase/supabase --skill react-hook-form -a cursor` (or -a gemini-cli, github-copilot or opencode for the others). To copy it by hand, put the folder in .cursor/skills/react-hook-form, .gemini/skills/react-hook-form, .github/skills/react-hook-form and .opencode/skills/react-hook-form in your project.
SKILL.md names no scripts, command-line tools or credentials: React Hook Form is instructions for the agent only.
SKILL.md contains no URLs. Any network use would come from the scripts or tools the agent runs. This is read from the text; nothing was executed.
Our automated static check of SKILL.md found no risky patterns, such as piping downloads into a shell, reading credential files or hidden Unicode. It is not a guarantee. Review the folder before installing.
React Hook Form is published under the Apache-2.0 licence (the repository's licence). It allows redistribution, so the full SKILL.md is shown on this page.
About 4k tokens (SKILL.md is roughly 16k characters). Agents keep only the skill's name and description in context until a task matches; then they load SKILL.md in full.
Skills that share tags, products or a category with React Hook Form: Saleor App UI (saleor/apps, 162 stars), CSS Modules (Opentrons/opentrons, 521 stars), Opentrons Typescript (Opentrons/opentrons, 521 stars) and React Router Development (remix-run/react-router, 57k stars). The comparison table on this page puts their stars, adoption, token cost, safety result and licence side by side.
supabase (a GitHub organization, an official publisher) maintains it in supabase/supabase, which has 111,222 GitHub stars. The repository holds 22 skills in this directory. The repository was last updated on October 8, 2026.
Source: supabase/supabase on GitHub. Facts on this page come from the repository at the commit we read; the author's words are quoted as theirs.