Component Patterns
When to Use This Skill
- Creating a new component
- Wrapping Ark UI primitives
- Setting up TypeScript types for variants
- Understanding the component → recipe relationship
Standard Component Structure
src/components/Button/
├── Button.tsx # Component implementation
├── Button.stories.tsx # Storybook stories
└── index.ts # Barrel export
Component Template
// src/components/Button/Button.tsx
import { forwardRef } from 'react';
import { Button as ArkButton } from '@ark-ui/react';
import { button, type ButtonVariantProps } from 'styled-system/recipes';
import { cx } from 'styled-system/css';
export interface ButtonProps
extends React.ComponentPropsWithoutRef<typeof ArkButton>,
ButtonVariantProps {
/** Optional left icon */
leftIcon?: React.ReactNode;
/** Optional right icon */
rightIcon?: React.ReactNode;
}
export const Button = forwardRef<HTMLButtonElement, ButtonProps>(
(
{
variant,
size,
leftIcon,
rightIcon,
className,
children,
...props
},
ref
) => {
return (
<ArkButton
ref={ref}
className={cx(button({ variant, size }), className)}
{...props}
>
{leftIcon}
{children}
{rightIcon}
</ArkButton>
);
}
);
Button.displayName = 'Button';
Barrel Export Pattern
// src/components/Button/index.ts
export { Button } from './Button';
export type { ButtonProps } from './Button';
// src/components/index.ts
export * from './Button';
export * from './Card';
export * from './Dialog';
Ark UI Component Patterns
Simple Components (Button, Card)
import { Button as ArkButton } from '@ark-ui/react';
// Ark Button is a simple primitive, just wrap and style
export const Button = forwardRef<HTMLButtonElement, ButtonProps>(
(props, ref) => (
<ArkButton ref={ref} className={button(props)} {...props} />
)
);
Compound Components (Dialog, Menu)
import { Dialog as ArkDialog } from '@ark-ui/react';
// Ark Dialog is compound - has Trigger, Content, etc.
export const Dialog = {
Root: ArkDialog.Root,
Trigger: forwardRef<HTMLButtonElement, DialogTriggerProps>(
({ className, ...props }, ref) => (
<ArkDialog.Trigger
ref={ref}
className={cx(dialogTrigger(), className)}
{...props}
/>
)
),
Content: forwardRef<HTMLDivElement, DialogContentProps>(
({ className, ...props }, ref) => (
<ArkDialog.Positioner>
<ArkDialog.Content
ref={ref}
className={cx(dialogContent(), className)}
{...props}
/>
</ArkDialog.Positioner>
)
),
// ... other parts
};
TypeScript Patterns
Variant Props from Recipe
import type { ButtonVariantProps } from 'styled-system/recipes';
// ButtonVariantProps is auto-generated by Panda CSS
// It includes: { variant?: 'filled' | 'outlined' | ... ; size?: 'sm' | 'md' | 'lg' }
Polymorphic Components (as prop)
// For components that can render as different elements
type AsProps<T extends React.ElementType> = {
as?: T;
};
type PolymorphicProps<T extends React.ElementType, Props = {}> =
Props & AsProps<T> & Omit<React.ComponentPropsWithoutRef<T>, keyof Props | 'as'>;
Ref Forwarding with generics
// When the element type can change
const Card = forwardRef(
<T extends React.ElementType = 'div'>(
{ as, ...props }: CardProps<T>,
ref: React.ForwardedRef<Element>
) => {
const Component = as || 'div';
return <Component ref={ref} {...props} />;
}
) as <T extends React.ElementType = 'div'>(
props: CardProps<T> & { ref?: React.ForwardedRef<Element> }
) => React.ReactElement;
Storybook Stories Pattern
// src/components/Button/Button.stories.tsx
import type { Meta, StoryObj } from '@storybook/react';
import { Button } from './Button';
const meta: Meta<typeof Button> = {
title: 'Components/Button',
component: Button,
tags: ['autodocs'],
argTypes: {
variant: {
control: 'select',
options: ['filled', 'outlined', 'text', 'elevated', 'tonal'],
},
size: {
control: 'select',
options: ['sm', 'md', 'lg'],
},
},
};
export default meta;
type Story = StoryObj<typeof Button>;
export const Filled: Story = {
args: {
children: 'Button',
variant: 'filled',
},
};
export const Outlined: Story = {
args: {
children: 'Button',
variant: 'outlined',
},
};
// Story for all variants
export const AllVariants: Story = {
render: () => (
<div style={{ display: 'flex', gap: '1rem', flexWrap: 'wrap' }}>
<Button variant="filled">Filled</Button>
<Button variant="outlined">Outlined</Button>
<Button variant="text">Text</Button>
<Button variant="elevated">Elevated</Button>
<Button variant="tonal">Tonal</Button>
</div>
),
};
Files to Reference