Adapters
Pre-built, security-hardened Zod schemas for popular UI libraries. You bring your own components — adapters define the schemas and action schemas that make them LLM-safe.
What Are Adapters?
An adapter is a factory function that returns a ComponentRegistry pre-loaded with Zod schemas for a specific UI library. The schemas define exactly which props and variants the LLM is allowed to produce — nothing more.
Adapters do not ship UI components. You provide your own Button, Card, Alert, and so on. The adapter only supplies the validated schema layer that sits between the LLM output and your component tree.
This separation means you keep full control over styling and behavior while GenUIKit handles validation, sanitization, and correction prompts.
Why adapters matter for security: Every prop value passes through a strict Zod schema before reaching your component. Free-form strings are sanitized, enum values are constrained, and unknown fields are stripped. This prevents the LLM from injecting arbitrary HTML, event handlers, or unexpected prop combinations.
GenUIKit ships three official adapters — shadcn/ui, Tailwind CSS, and Material UI — each covering 30 component types. The first 10 keep library-specific naming and variant options, while 20 advanced schemas are shared across every adapter.
shadcn/ui Adapter
The shadcn/ui adapter maps to the component names and variant conventions used by shadcn/ui. Install the adapters package if you haven't already:
| 1 | npm install @genuikit/adapters |
Setup
Import createShadcnRegistry from the shadcn sub-path and pass your component map:
| 1 | import { createShadcnRegistry } from '@genuikit/adapters/shadcn'; |
| 2 | import { |
| 3 | Button, Card, Alert, Badge, Input, |
| 4 | Select, Dialog, Tabs, Table, Avatar, |
| 5 | } from '@/components/ui'; |
| 6 | |
| 7 | const registry = createShadcnRegistry({ |
| 8 | Button, |
| 9 | Card, |
| 10 | Alert, |
| 11 | Badge, |
| 12 | Input, |
| 13 | Select, |
| 14 | Dialog, |
| 15 | Tabs, |
| 16 | Table, |
| 17 | Avatar, |
| 18 | }); |
| 19 | |
| 20 | export default registry; |
This shortened example shows the library-specific base set. Your actual registry should also provide the shared advanced components such as Accordion,Form, BarChart, and Map.
Included Components
The shadcn/ui adapter registers schemas for 30 components. The first 10 constrain the base primitives to match shadcn/ui conventions, and the remaining 20 cover advanced layout, form, visualization, and map patterns:
- --Button — variant:
default,destructive,outline,secondary,ghost,link; size:default,sm,lg,icon - --Card — title, description, content, and footer sections
- --Alert — variant:
default,destructive; title and description - --Badge — variant:
default,secondary,destructive,outline - --Input — type, placeholder, disabled, required
- --Select — options array, placeholder, disabled
- --Dialog — title, description, open state, content
- --Tabs — tabs array with label and content per tab
- --Table — headers array, rows as 2D array, caption
- --Avatar — src, alt, fallback text
Tailwind CSS Adapter
The Tailwind adapter is library-agnostic — it uses generic variant names like primary, secondary, and danger that map naturally to utility-class-based components. It registers the same 30 component types.
Setup
| 1 | import { createTailwindRegistry } from '@genuikit/adapters/tailwind'; |
| 2 | import { |
| 3 | Button, Card, Alert, Badge, Input, |
| 4 | Select, Dialog, Tabs, Table, Avatar, |
| 5 | } from '@/components/ui'; |
| 6 | |
| 7 | const registry = createTailwindRegistry({ |
| 8 | Button, |
| 9 | Card, |
| 10 | Alert, |
| 11 | Badge, |
| 12 | Input, |
| 13 | Select, |
| 14 | Dialog, |
| 15 | Tabs, |
| 16 | Table, |
| 17 | Avatar, |
| 18 | }); |
| 19 | |
| 20 | export default registry; |
The shared advanced set is identical here too: Accordion, Breadcrumbs, Pagination, ProgressBar, Skeleton, Tooltip, Textarea, Checkbox, RadioGroup, Switch, Slider, Form, Stepper, StatCard, Timeline, BarChart, LineChart, PieChart, AreaChart, and Map.
Variant Differences
The Tailwind adapter uses simplified, library-agnostic variant names:
- --Button — variant:
primary,secondary,danger,outline,ghost; size:sm,md,lg - --Alert — variant:
info,success,warning,danger - --Badge — variant:
primary,secondary,danger,outline - --All other components share the same schema shape as the shadcn adapter
Material UI Adapter
The MUI adapter follows Material UI naming conventions. Some component names differ from the other adapters to match what MUI developers expect.
Setup
| 1 | import { createMuiRegistry } from '@genuikit/adapters/mui'; |
| 2 | import { |
| 3 | Button, Card, Alert, Chip, TextField, |
| 4 | Select, Dialog, Tabs, Table, Avatar, |
| 5 | } from '@mui/material'; |
| 6 | |
| 7 | const registry = createMuiRegistry({ |
| 8 | Button, |
| 9 | Card, |
| 10 | Alert, |
| 11 | Chip, // MUI uses Chip instead of Badge |
| 12 | TextField, // MUI uses TextField instead of Input |
| 13 | Select, |
| 14 | Dialog, |
| 15 | Tabs, |
| 16 | Table, |
| 17 | Avatar, |
| 18 | }); |
| 19 | |
| 20 | export default registry; |
The MUI adapter uses native Material components where they exist, and custom wrappers for the shared advanced schemas such as charts, timelines, stat cards, and maps.
MUI-specific Naming
Key differences from the other adapters:
- --Button — variant:
text,contained,outlined; color:primary,secondary,error,warning,info,success; size:small,medium,large - --Chip (instead of Badge) — variant:
filled,outlined; color:default,primary,secondary,error,warning,info,success - --TextField (instead of Input) — variant:
filled,outlined,standard; includes label and helperText props - --Alert — severity:
error,warning,info,success; variant:filled,outlined,standard
Component Comparison
All three adapters share 20 advanced schemas on top of the original 10 primitives. This table focuses on how naming and variants differ across the core primitives:
| Component | shadcn/ui | Tailwind | Material UI |
|---|---|---|---|
| Button | default, destructive, outline, secondary, ghost, link | primary, secondary, danger, outline, ghost | text, contained, outlined |
| Card | title, description, content, footer | title, description, content, footer | title, description, content, footer |
| Alert | default, destructive | info, success, warning, danger | error, warning, info, success |
| Badge / Chip | Badge: default, secondary, destructive, outline | Badge: primary, secondary, danger, outline | Chip: filled, outlined (+ color prop) |
| Input / TextField | Input: type, placeholder, disabled | Input: type, placeholder, disabled | TextField: filled, outlined, standard |
| Select | options[], placeholder | options[], placeholder | options[], label, variant |
| Dialog | title, description, open | title, description, open | title, description, open, maxWidth |
| Tabs | tabs[]: label, content | tabs[]: label, content | tabs[]: label, content, icon |
| Table | headers[], rows[][], caption | headers[], rows[][], caption | headers[], rows[][], caption, stickyHeader |
| Avatar | src, alt, fallback | src, alt, fallback | src, alt, variant: circular, rounded, square |
Shared advanced components across all adapters: Accordion, Breadcrumbs, Pagination, ProgressBar, Skeleton, Tooltip, Textarea, Checkbox, RadioGroup, Switch, Slider, Form, Stepper, StatCard, Timeline, BarChart, LineChart, PieChart, AreaChart, and Map.
Customizing Schemas
Adapter schemas are standard Zod objects. You can extend them with .extend() to add custom fields, override defaults, or tighten constraints before registering.
Extending a schema
Import the raw schema from the adapter, extend it, then register the customized version:
| 1 | import { z } from 'zod'; |
| 2 | import { buttonSchema } from '@genuikit/adapters/shadcn'; |
| 3 | import { ComponentRegistry } from '@genuikit/core'; |
| 4 | import { Button } from '@/components/ui/button'; |
| 5 | |
| 6 | // Add icon and tooltip props to the default button schema |
| 7 | const extendedButtonSchema = buttonSchema.extend({ |
| 8 | icon: z.enum(['plus', 'trash', 'edit', 'download']).optional(), |
| 9 | tooltip: z.string().max(100).optional(), |
| 10 | }); |
| 11 | |
| 12 | const registry = new ComponentRegistry(); |
| 13 | registry.register('Button', extendedButtonSchema, Button); |
Overriding defaults
Use .extend() with a field that already exists to replace its definition. For example, restrict button variants to a smaller set:
| 1 | import { z } from 'zod'; |
| 2 | import { buttonSchema } from '@genuikit/adapters/shadcn'; |
| 3 | |
| 4 | // Only allow 'default' and 'outline' variants in your app |
| 5 | const restrictedButton = buttonSchema.extend({ |
| 6 | variant: z.enum(['default', 'outline']).default('default'), |
| 7 | }); |
Adding action schemas
Action schemas define callbacks the LLM can attach to interactive components. They are registered separately from prop schemas:
| 1 | import { z } from 'zod'; |
| 2 | import { ComponentRegistry } from '@genuikit/core'; |
| 3 | import { buttonSchema } from '@genuikit/adapters/shadcn'; |
| 4 | import { Button } from '@/components/ui/button'; |
| 5 | |
| 6 | const buttonActionSchema = z.object({ |
| 7 | onClick: z.object({ |
| 8 | action: z.enum(['navigate', 'submit', 'dismiss']), |
| 9 | payload: z.record(z.string()).optional(), |
| 10 | }).optional(), |
| 11 | }); |
| 12 | |
| 13 | const registry = new ComponentRegistry(); |
| 14 | registry.register('Button', buttonSchema, Button, { |
| 15 | actionSchema: buttonActionSchema, |
| 16 | }); |
Creating Custom Adapters
If none of the built-in adapters fit your design system, you can create your own using createRegistryFactory from @genuikit/adapters. This gives you the same factory pattern the official adapters use.
Factory function
Define your component schemas, then wrap them in a factory that accepts a component map:
| 1 | import { z } from 'zod'; |
| 2 | import { createRegistryFactory } from '@genuikit/adapters'; |
| 3 | |
| 4 | // Define schemas for your design system |
| 5 | const schemas = { |
| 6 | Button: z.object({ |
| 7 | label: z.string().max(50), |
| 8 | intent: z.enum(['primary', 'neutral', 'caution']).default('neutral'), |
| 9 | size: z.enum(['xs', 'sm', 'md', 'lg']).default('md'), |
| 10 | disabled: z.boolean().default(false), |
| 11 | }), |
| 12 | Card: z.object({ |
| 13 | heading: z.string().max(120), |
| 14 | body: z.string().max(2000), |
| 15 | elevated: z.boolean().default(false), |
| 16 | }), |
| 17 | Notice: z.object({ |
| 18 | message: z.string().max(500), |
| 19 | level: z.enum(['info', 'warn', 'error', 'success']).default('info'), |
| 20 | dismissible: z.boolean().default(true), |
| 21 | }), |
| 22 | }; |
| 23 | |
| 24 | // Create the factory — consumers call this with their component map |
| 25 | export const createMyRegistry = createRegistryFactory(schemas); |
Using the custom adapter
Consumers of your adapter use it exactly like the built-in adapters:
| 1 | import { createMyRegistry } from '@my-org/design-system-adapter'; |
| 2 | import { Button, Card, Notice } from '@my-org/design-system'; |
| 3 | |
| 4 | const registry = createMyRegistry({ |
| 5 | Button, |
| 6 | Card, |
| 7 | Notice, |
| 8 | }); |
| 9 | |
| 10 | export default registry; |
Including action schemas
Pass a second argument to createRegistryFactory to define action schemas alongside your component schemas:
| 1 | import { z } from 'zod'; |
| 2 | import { createRegistryFactory } from '@genuikit/adapters'; |
| 3 | |
| 4 | const schemas = { |
| 5 | Button: z.object({ |
| 6 | label: z.string().max(50), |
| 7 | intent: z.enum(['primary', 'neutral', 'caution']).default('neutral'), |
| 8 | }), |
| 9 | }; |
| 10 | |
| 11 | const actionSchemas = { |
| 12 | Button: z.object({ |
| 13 | onClick: z.object({ |
| 14 | action: z.enum(['navigate', 'submit', 'dismiss']), |
| 15 | url: z.string().url().optional(), |
| 16 | }).optional(), |
| 17 | }), |
| 18 | }; |
| 19 | |
| 20 | export const createMyRegistry = createRegistryFactory(schemas, actionSchemas); |