@eidhachem/forms-shadcn-vue
v0.1.1
Published
Official @eidhachem/forms UI adapter for shadcn-vue: maps built-in field types to shadcn-vue components.
Downloads
45
Maintainers
Readme
@sowlutions/forms-shadcn-vue
Status:
createShadcnVueAdapter()maps a high-confidence core subset of@sowlutions/forms's built-in field types onto real shadcn-vue components (built on Reka UI primitives + Tailwind CSS). See ADR-024 for the full per-type breakdown and why the mapped set is smaller than@sowlutions/forms-nuxt-ui's.
Setup
shadcn-vue has no installable component library at all: its CLI
(npx shadcn-vue add ...) copies component source directly into your own
app (typically src/components/ui/*), which you then own. There is no
fixed package this adapter could import components from even if it wanted
to — so, like @sowlutions/forms-nuxt-ui (for an unrelated reason), it takes
your already-imported components as a parameter, once:
npx shadcn-vue@latest add button input textarea checkbox switch radio-group select slider label// e.g. src/plugins/forms-shadcn-vue.ts
import { Label } from '@/components/ui/label';
import { Input } from '@/components/ui/input';
import { Textarea } from '@/components/ui/textarea';
import { Checkbox } from '@/components/ui/checkbox';
import { Switch } from '@/components/ui/switch';
import { RadioGroup, RadioGroupItem } from '@/components/ui/radio-group';
import { Select, SelectTrigger, SelectValue, SelectContent, SelectItem } from '@/components/ui/select';
import { Slider } from '@/components/ui/slider';
import { createShadcnVueAdapter } from '@sowlutions/forms-shadcn-vue';
import { registerAdapter, setDefaultAdapter } from '@sowlutions/forms';
registerAdapter(
createShadcnVueAdapter({
Label,
Input,
Textarea,
Checkbox,
Switch,
RadioGroup,
RadioGroupItem,
Select,
SelectTrigger,
SelectValue,
SelectContent,
SelectItem,
Slider,
}),
);
setDefaultAdapter('shadcn-vue');From then on, FormRenderer/FieldRenderer render every mapped field type
through the real shadcn-vue component — no further wiring per form or per
field. This works the same in Nuxt (@sowlutions/forms-nuxt) or a plain Vue
app; nothing here is Nuxt-specific.
Why not shadcn-vue's own Form components?
shadcn-vue also ships Form/FormField/FormItem/FormLabel/
FormMessage — but those are built directly on
vee-validate, a different form
library, and aren't used here. This adapter's label/description/error
layout is instead a small, deliberately plain replacement
(FieldShell, internal) built from shadcn-vue's own Label primitive —
see ADR-024.
Supported field types
text, password, email, tel, url, search, number, textarea,
checkbox, switch, radio, select, range.
Deliberately unmapped — supports() returns false and
resolveFieldComponentOrThrow throws a clear error rather than rendering
silently-wrong UI (rule 11): multiselect/autocomplete/combobox/
checkbox-group/tags/color/rating/otp/currency/percentage/the
file/image/file-list/image-list family (no verified, high-
confidence shadcn-vue component was installed/tested for these — a real
project needing one can extend this adapter, see ADR-024); phone (no
native component); the date/time family (needs @internationalized/date-
shaped values, same reasoning as ADR-018); structural types; custom
(bypasses adapter resolution, ADR-013 §3).
Passing per-field config (options, min/max/step, ...)
Same two channels as @sowlutions/forms-nuxt-ui (see ADR-023): field.props
and the field's typed per-type config both merge into the resolved
component's props, props winning on a collision.
{ name: 'country', type: 'select', props: { options: [{ label: 'USA', value: 'us' }] } }
{ name: 'volume', type: 'range', props: { min: 0, max: 10, step: 1 } }radio/select read options (an array of @sowlutions/forms'
SelectOption — label/value/disabled/description) and compose one
RadioGroupItem/SelectItem per entry themselves — see ADR-024 for why
this needed a different wrapper shape than Nuxt UI's single generic one.
range's value is a single number; the underlying Reka UI Slider
always models an array (even for one thumb) — this adapter wraps/unwraps
that conversion, so field.props's only relevant keys are min/max/
step.
See the repository root README and docs/architecture for the full architecture and roadmap.
