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:

1npm install @genuikit/adapters

Setup

Import createShadcnRegistry from the shadcn sub-path and pass your component map:

registry.tstypescript
1import { createShadcnRegistry } from '@genuikit/adapters/shadcn';
2import {
3 Button, Card, Alert, Badge, Input,
4 Select, Dialog, Tabs, Table, Avatar,
5} from '@/components/ui';
6 
7const registry = createShadcnRegistry({
8 Button,
9 Card,
10 Alert,
11 Badge,
12 Input,
13 Select,
14 Dialog,
15 Tabs,
16 Table,
17 Avatar,
18});
19 
20export 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

registry.tstypescript
1import { createTailwindRegistry } from '@genuikit/adapters/tailwind';
2import {
3 Button, Card, Alert, Badge, Input,
4 Select, Dialog, Tabs, Table, Avatar,
5} from '@/components/ui';
6 
7const registry = createTailwindRegistry({
8 Button,
9 Card,
10 Alert,
11 Badge,
12 Input,
13 Select,
14 Dialog,
15 Tabs,
16 Table,
17 Avatar,
18});
19 
20export 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

registry.tstypescript
1import { createMuiRegistry } from '@genuikit/adapters/mui';
2import {
3 Button, Card, Alert, Chip, TextField,
4 Select, Dialog, Tabs, Table, Avatar,
5} from '@mui/material';
6 
7const 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 
20export 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:

Componentshadcn/uiTailwindMaterial UI
Buttondefault, destructive, outline, secondary, ghost, linkprimary, secondary, danger, outline, ghosttext, contained, outlined
Cardtitle, description, content, footertitle, description, content, footertitle, description, content, footer
Alertdefault, destructiveinfo, success, warning, dangererror, warning, info, success
Badge / ChipBadge: default, secondary, destructive, outlineBadge: primary, secondary, danger, outlineChip: filled, outlined (+ color prop)
Input / TextFieldInput: type, placeholder, disabledInput: type, placeholder, disabledTextField: filled, outlined, standard
Selectoptions[], placeholderoptions[], placeholderoptions[], label, variant
Dialogtitle, description, opentitle, description, opentitle, description, open, maxWidth
Tabstabs[]: label, contenttabs[]: label, contenttabs[]: label, content, icon
Tableheaders[], rows[][], captionheaders[], rows[][], captionheaders[], rows[][], caption, stickyHeader
Avatarsrc, alt, fallbacksrc, alt, fallbacksrc, 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:

registry.tstypescript
1import { z } from 'zod';
2import { buttonSchema } from '@genuikit/adapters/shadcn';
3import { ComponentRegistry } from '@genuikit/core';
4import { Button } from '@/components/ui/button';
5 
6// Add icon and tooltip props to the default button schema
7const extendedButtonSchema = buttonSchema.extend({
8 icon: z.enum(['plus', 'trash', 'edit', 'download']).optional(),
9 tooltip: z.string().max(100).optional(),
10});
11 
12const registry = new ComponentRegistry();
13registry.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:

registry.tstypescript
1import { z } from 'zod';
2import { buttonSchema } from '@genuikit/adapters/shadcn';
3 
4// Only allow 'default' and 'outline' variants in your app
5const 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:

registry.tstypescript
1import { z } from 'zod';
2import { ComponentRegistry } from '@genuikit/core';
3import { buttonSchema } from '@genuikit/adapters/shadcn';
4import { Button } from '@/components/ui/button';
5 
6const 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 
13const registry = new ComponentRegistry();
14registry.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:

my-adapter.tstypescript
1import { z } from 'zod';
2import { createRegistryFactory } from '@genuikit/adapters';
3 
4// Define schemas for your design system
5const 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
25export const createMyRegistry = createRegistryFactory(schemas);

Using the custom adapter

Consumers of your adapter use it exactly like the built-in adapters:

registry.tstypescript
1import { createMyRegistry } from '@my-org/design-system-adapter';
2import { Button, Card, Notice } from '@my-org/design-system';
3 
4const registry = createMyRegistry({
5 Button,
6 Card,
7 Notice,
8});
9 
10export default registry;

Including action schemas

Pass a second argument to createRegistryFactory to define action schemas alongside your component schemas:

my-adapter.tstypescript
1import { z } from 'zod';
2import { createRegistryFactory } from '@genuikit/adapters';
3 
4const schemas = {
5 Button: z.object({
6 label: z.string().max(50),
7 intent: z.enum(['primary', 'neutral', 'caution']).default('neutral'),
8 }),
9};
10 
11const 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 
20export const createMyRegistry = createRegistryFactory(schemas, actionSchemas);

Next Steps