Design System
Build consistent, accessible, and maintainable UIs using the Chakra UI-based Design System with PM-prefixed components and theme tokens.
Core Principles
- Token-based design: All styling uses theme tokens (colors, spacing, typography) - never hard-coded values
- Component wrapping: Wrap Chakra UI primitives with custom PM-prefixed components
- Accessibility first: Leverage Chakra's built-in ARIA support and keyboard navigation
- Centralized exports: Import PM components from a central index to prevent direct Chakra UI usage
- Design-code sync: Theme tokens align with Figma design tokens
Available Components
All PM components are exported from @packmind/ui:
Typography
PMHeading- Headings (h1-h6)PMText- Body text and paragraphsPMLink- Links and navigation
Form Components
PMButton- Buttons with variants (primary, secondary, tertiary, outline, ghost)PMInput- Text inputsPMTextArea- Multi-line text inputsPMLabel- Form field labelsPMFormContainer- Form layout wrapperPMNativeSelect- Dropdown selectPMMenu- Menu and dropdown componentsPMDialog- Modal dialogs
Layout Components
Exported from lib/components/layout
Content Components
Exported from lib/components/content (includes PMTable)
Feedback Components
Exported from lib/components/feedback
Navigation Components
Exported from lib/components/navigation (includes PMTabs)
Creating Components
Workflow
- Never import Chakra UI directly in feature code - always wrap with PM components
- Define the PM component in
packages/ui/src/lib/components/ - Use forwardRef to support ref forwarding
- Extend Chakra props to inherit ARIA, event handlers, and style props
- Use theme tokens via curly brace syntax:
{colors.background.primary} - Export from index at
packages/ui/src/index.ts
Component Template
import { Button, ButtonProps } from '@chakra-ui/react';
import { forwardRef } from 'react';
interface IPMButtonProps extends ButtonProps {
variant?: 'primary' | 'secondary' | 'tertiary' | 'outline' | 'ghost';
children: React.ReactNode;
}
export const PMButton = forwardRef<HTMLButtonElement, IPMButtonProps>(
(props, ref) => {
const { children, variant = 'primary', ...buttonProps } = props;
return (
<Button ref={ref} {...buttonProps} variant={variant}>
{children}
</Button>
);
}
);
Key Patterns
DO:
✅ Wrap Chakra primitives (Box, Flex, Text, Button)
✅ Use theme tokens:
{colors.blue.500},{spacing.4}✅ Extend Chakra component props interfaces
✅ Use forwardRef for ref forwarding
✅ Export from central index file
✅ Include ARIA attributes automatically via Chakra
DON'T:
❌ Hard-code colors:
#FF5733,rgb(255, 87, 51)❌ Hard-code spacing:
padding: 16px,margin: 1rem❌ Create raw HTML elements:
<div style={{...}}>❌ Import Chakra UI directly in app code
❌ Use inline styles
Theme Configuration
Theme is defined in packages/ui/src/lib/theme/theme.ts using defineConfig:
import { defineConfig } from '@chakra-ui/react';
export const packmindTheme = defineConfig({
globalCss: {
'html, body': {
backgroundColor: '{colors.background.secondary}',
color: '{colors.text.primary}',
},
},
cssVarsPrefix: 'pm',
theme: {
tokens: {
colors: {
beige: { /* color scale */ },
blue: { /* color scale */ },
orange: { /* color scale */ },
// Semantic tokens
background: {
primary: { value: '{colors.beige.0}' },
secondary: { value: '{colors.beige.50}' },
},
text: {
primary: { value: '{colors.beige.1000}' },
},
},
spacing: { /* spacing scale */ },
fonts: { /* font definitions */ },
},
recipes: {
button: pmButtonRecipe,
heading: pmHeadingRecipe,
// Component-specific styling
},
},
});
Application Setup
Wrap the app root with UIProvider:
import { UIProvider } from '@packmind/ui';
function App() {
return (
<UIProvider>
{/* Your app content */}
</UIProvider>
);
}
The UIProvider wraps ChakraProvider and configures the theme system.
Extending the Theme
Adding New Colors
// In theme.ts
theme: {
tokens: {
colors: {
// Add new brand color
purple: {
500: { value: '#8B5CF6' },
600: { value: '#7C3AED' },
},
// Add semantic token
background: {
tertiary: { value: '{colors.purple.500}' },
},
},
},
}
Adding Component Recipes
Create a .recipe.ts file for component-specific styling:
// PMButton.recipe.ts
import { defineRecipe } from '@chakra-ui/react';
export const pmButtonRecipe = defineRecipe({
base: {
borderRadius: 'md',
fontWeight: 'medium',
},
variants: {
variant: {
primary: {
bg: '{colors.blue.500}',
color: 'white',
_hover: { bg: '{colors.blue.600}' },
},
secondary: {
bg: '{colors.orange.500}',
color: 'white',
_hover: { bg: '{colors.orange.600}' },
},
},
},
});
Common Patterns
Creating Layout Components
import { Box, Flex } from '@chakra-ui/react';
import { forwardRef } from 'react';
interface IPMCardProps {
children: React.ReactNode;
padding?: string;
}
export const PMCard = forwardRef<HTMLDivElement, IPMCardProps>(
({ children, padding = '4' }, ref) => {
return (
<Box
ref={ref}
bg="{colors.background.primary}"
borderRadius="md"
padding={padding}
boxShadow="sm"
>
{children}
</Box>
);
}
);
Using Responsive Tokens
<PMBox
padding={{ base: '2', md: '4', lg: '6' }}
fontSize={{ base: 'sm', md: 'md' }}
>
Responsive content
</PMBox>
Composing Components
import { PMButton, PMText } from '@packmind/ui';
export const MyFeature = () => {
return (
<div>
<PMText variant="body">Click the button</PMText>
<PMButton variant="primary" => {}}>
Submit
</PMButton>
</div>
);
};
Storybook Documentation
Every new component must have a Storybook story. See references/storybook-guide.md for details.
Common Issues
Hard-coded Values
Problem:
<div style={{ color: '#FF5733', padding: '16px' }}>
Solution:
import { Box } from '@chakra-ui/react';
<Box color="{colors.orange.500}" padding="4">
Direct Chakra Imports
Problem:
import { Button } from '@chakra-ui/react';
Solution:
import { PMButton } from '@packmind/ui';
Missing forwardRef
Problem:
export const PMButton = (props) => <Button {...props} />;
Solution:
export const PMButton = forwardRef<HTMLButtonElement, IPMButtonProps>(
(props, ref) => <Button ref={ref} {...props} />
);
Resources
Storybook Guide
Component Examples
Full standard: React Components with Chakra UI Design System