component-patterns
React component patterns with Ark UI, forwardRef, and TypeScript for the design system
Installation
Follow the repository instructions for this skill and your agent. Review the files and dependencies before adding it to your workflow.
View source ↗GuidesSkill instructions
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
-
src/components/Button/Button.tsx- Existing implementation (for reference) - Ark UI docs: https://ark-ui.com/react/docs/overview/introduction
Examples
Read the usage examples in the source repository.
View source ↗Source history
- Indexed on
- 2026-01-20
- Index updated
- 2026-01-26
- Branch
- main
Index and sync dates describe this directory. View the source repository for code history.
GitHub ↗chat Comments (0)
Sign in to join the discussion and leave a comment.
No comments yet. Be the first to share your thoughts!