Component Design
How to design, wrap, or transform components to the mdxui interface - covers TDD with pixel-perfect designs, wrapping external components, and transforming internal designs to mdxui-compliant types
Component Design
How to create mdxui-compliant components through wrapping external libraries or transforming internal designs.
Core Principle
Templates are opinionated implementations of the abstract mdxui interface.
// Any template exports the standard interface
export { components } from '@mdxui/beacon' // Site template
export { components } from '@mdxui/cockpit' // App template
export { components } from '@mdxui/tremor' // Tremor-based template
export { components } from '@mdxui/custom' // Your custom templateSame MDX content, completely different rendering. The interface is the contract.
Two Patterns
1. WRAP (External Components)
When: Using external libraries (Tremor, shadcn, Radix, etc.)
Rule: Code and styles do NOT change. Only component names and props change to match mdxui types/schemas.
// DON'T modify Tremor's implementation
// DO wrap it to mdxui interface
// tremor-adapter.tsx
import { Card as TremorCard, Title, Text } from '@tremor/react'
import type { CardProps } from 'mdxui'
export const Card: React.FC<CardProps> = ({
title,
description,
children,
...props
}) => (
<TremorCard {...props}>
{title && <Title>{title}</Title>}
{description && <Text>{description}</Text>}
{children}
</TremorCard>
)2. TRANSFORM (Internal Components)
When: Designed components that weren't built mdxui-first
Rule: Migrate to mdxui-compliant types. This is a transformation, not a wrap.
// Before: Original design
export const FeatureSection = ({
headline,
items,
layout
}: BryantFeatureProps) => { ... }
// After: Transform to mdxui interface
export const Features: React.FC<FeaturesProps> = ({
title, // was: headline
features, // was: items
columns, // was: layout
variant,
}) => {
// Implementation may change to support mdxui props
...
}Ideal Workflow
Best: Design directly in mdxui components from the start.
Acceptable: Design fast, transform later.
Design Phase (speed priority) Migration Phase (compliance)
+--------------------------+ +--------------------------+
| Design whatever is | -> | Transform to mdxui |
| fastest | | types and schemas |
+--------------------------+ +--------------------------+If designing in mdxui-style slows down the design process, transformation is an acceptable second step.
TDD with Pixel-Perfect Designs
The Process
1. Start with Design
- Figma mockup OR
- External component (Tremor, etc.) OR
- Existing implementation
2. Create Visual Test
- Screenshot/visual regression as the "spec"
3. Implement mdxui Interface
- Define props matching mdxui types
- Wrap or transform the implementation
- Verify visual output matches design
4. Test Passes When
- Visual output is pixel-perfect to originalVisual Regression as TDD
// The design IS the test
describe('Hero', () => {
it('matches design spec', async () => {
render(<Hero {...mockProps} />)
await expect(page).toMatchSnapshot('hero-default')
})
it('matches dark mode design', async () => {
render(<Hero {...mockProps} />, { theme: 'dark' })
await expect(page).toMatchSnapshot('hero-dark')
})
})The mdxui Contract
Every component must satisfy its mdxui type:
import type { HeroProps, FeaturesProps, CardProps } from 'mdxui'
import { HeroPropsSchema } from 'mdxui/zod'
// Type-safe props
export const Hero: React.FC<HeroProps> = (props) => {
// Runtime validation (optional but recommended)
const validated = HeroPropsSchema.parse(props)
return <...>
}Template Structure
@mdxui/[template-name]/
├── src/
│ ├── components/
│ │ ├── site/ # SiteComponents implementations
│ │ │ ├── Hero.tsx
│ │ │ ├── Features.tsx
│ │ │ └── Pricing.tsx
│ │ ├── app/ # AppComponents implementations
│ │ │ ├── Dashboard.tsx
│ │ │ └── Sidebar.tsx
│ │ └── shared/ # Shared primitives
│ ├── index.ts # exports { components }
│ └── adapters/ # External library adapters (if wrapping)
│ ├── tremor.tsx
│ └── shadcn.tsx
└── package.jsonExport Pattern
// @mdxui/beacon/src/index.ts
import type { SiteComponents } from 'mdxui'
import { Hero } from './components/site/Hero'
import { Features } from './components/site/Features'
import { Pricing } from './components/site/Pricing'
// ... all site components
export const components: SiteComponents = {
Hero,
Features,
Pricing,
// ... complete interface
}
// Usage in any project
import { components } from '@mdxui/beacon'
// OR
import { components } from '@mdxui/cockpit'
// OR
import { components } from '@mdxui/custom'
// Same MDX, different rendering
<MDXProvider components={components}>
<Content />
</MDXProvider>Wrapping External Libraries
Tremor Example
// adapters/tremor/card.tsx
import { Card as TremorCard } from '@tremor/react'
import type { CardProps } from 'mdxui'
// Map mdxui props to Tremor props
export const Card: React.FC<CardProps> = ({
title,
description,
image,
actions,
variant,
children,
}) => (
<TremorCard
decoration={variant === 'highlighted' ? 'top' : undefined}
decorationColor={variant === 'highlighted' ? 'blue' : undefined}
>
{image && <img src={image} alt="" />}
{title && <h3>{title}</h3>}
{description && <p>{description}</p>}
{children}
{actions && <div className="flex gap-2">{actions}</div>}
</TremorCard>
)shadcn Example
// adapters/shadcn/button.tsx
import { Button as ShadcnButton } from '@/components/ui/button'
import type { ButtonProps } from 'mdxui'
export const Button: React.FC<ButtonProps> = ({
label,
variant = 'default',
size = 'default',
icon,
loading,
...props
}) => (
<ShadcnButton
variant={variant}
size={size}
disabled={loading}
{...props}
>
{loading && <Spinner />}
{icon && <Icon name={icon} />}
{label}
</ShadcnButton>
)Transforming Internal Components
Before/After Example
// BEFORE: Quick prototype
// components/landing/feature-grid.tsx
export function FeatureGrid({
headline,
subhead,
items,
cols = 3
}) {
return (
<section className="py-24">
<h2 className="text-4xl font-bold">{headline}</h2>
<p className="text-xl text-gray-600">{subhead}</p>
<div className={`grid grid-cols-${cols} gap-8`}>
{items.map(item => (
<div key={item.id}>
<Icon name={item.icon} />
<h3>{item.title}</h3>
<p>{item.desc}</p>
</div>
))}
</div>
</section>
)
}
// AFTER: Transformed to mdxui
// components/site/Features.tsx
import type { FeaturesProps, Feature } from 'mdxui'
export const Features: React.FC<FeaturesProps> = ({
title, // was: headline
subtitle, // was: subhead
features, // was: items
columns = 3, // was: cols
variant = 'grid',
}) => {
return (
<section className="py-24">
<h2 className="text-4xl font-bold">{title}</h2>
{subtitle && <p className="text-xl text-gray-600">{subtitle}</p>}
<div className={`grid grid-cols-${columns} gap-8`}>
{features.map((feature: Feature) => (
<div key={feature.name}>
{feature.icon && <Icon name={feature.icon} />}
<h3>{feature.name}</h3>
{feature.description && <p>{feature.description}</p>}
</div>
))}
</div>
</section>
)
}Transformation Checklist
- Props renamed to mdxui standard names
- Props typed with mdxui interfaces
- Optional Zod validation added
- Component exported with mdxui name
- Visual output unchanged (pixel-perfect)
- Tests updated to use new props
Quick Reference
| Scenario | Pattern | Code Changes? | Style Changes? |
|---|---|---|---|
| External lib (Tremor) | Wrap | No | No |
| External lib (shadcn) | Wrap | No | No |
| Internal design | Transform | Maybe | No |
| New component | Build mdxui-first | N/A | N/A |
Anti-Patterns
DON'T:
- Modify external library source code
- Change visual design during wrap/transform
- Skip type compliance for speed
- Create one-off props that don't match mdxui
DO:
- Keep external styles intact
- Map props to mdxui interface
- Validate with Zod schemas
- Test for visual regression
Related
- SiteComponents Reference - Complete SiteComponents interface
- AppComponents Reference - Complete AppComponents interface
- Zod Schemas - All available validation schemas