Zest Design System Integration - Web
Integration patterns for the Zest design system (@/libs/zest components + @/libs/zest-support utilities) in React web applications.
Documentation
This skill has comprehensive documentation:
- •Production Examples - Real-world code examples from the codebase
- •API Reference - Complete API documentation with official links
- •Implementation Patterns - Best practices and anti-patterns
When to Use
Use Zest components for:
- •Standard layouts, spacing, and typography
- •Design system adherence and consistency
- •Responsive design with breakpoint arrays
- •Theme-aware components with design tokens
- •Form inputs and interactive elements
Use styled-components for:
- •Custom styling that doesn't exist in Zest
- •Complex animations and transitions
- •Component-specific styles beyond Zest capabilities
Provider Setup
Zest components require ZestProvider at the app root:
// app/spaces/_app/modules/shell/index.tsx
import { ZestProvider } from '@/libs/zest-support';
<ZestProvider
{...(ssrPayload?.customBrand && {
customBrand: ssrPayload.customBrand,
})}
>
{children}
</ZestProvider>
Core Principles
1. Box Component for Layout
Box is the foundational layout component with extensive styling props.
✅ Good:
// app/spaces/whitelabel/modules/whitelabel-web/packages/pages/cancellation/src/pages/Cancellation.tsx
import { Box, Text, Divider } from '@/libs/zest';
<Box
display="flex"
gap="md-1"
alignItems="stretch"
flexDirection={['column', 'column', 'row']}
>
{deflectionCardsToRender.map((deflectionCardKey) => (
<Box flex="1" alignSelf="stretch" key={deflectionCardKey}>
<CardComponent />
</Box>
))}
</Box>
// With padding and border radius
<Box
py={['md-1', 'md-1', 'md-3']}
px={['sm-1', 'sm-2', 'sm-2']}
backgroundColor="neutral.100"
borderRadius="border-radius-lg"
boxShadow="shadow-xl"
>
{/* Content */}
</Box>
Why: Box provides all layout props (flexbox, grid, spacing, positioning) with theme tokens.
2. Responsive Array Syntax
Use arrays for responsive values: [mobile, tablet, desktop].
✅ Good:
// app/spaces/whitelabel/modules/whitelabel-web/packages/pages/my-deliveries/menus/EditMenuCollections/components/EmptyMenu.tsx:22
<Box
width={['100%', '100%', '33.33%']}
flex={['0 0 100%', '0 0 100%', '0 0 33.33%']}
p={['zero', 'sm-2']}
pb="sm-2"
>
{/* Content */}
</Box>
Common responsive patterns:
// Layout changes
<Box flexDirection={['column', 'row']}>
// Visibility
<Box display={['none', 'flex']}>
// Conditional display based on state
<Box display={[
shouldHideOnMobile ? 'none' : 'flex',
'flex',
]}>
// Spacing
<Box
marginBottom={['md-2', 'lg-2']}
paddingX={['sm-1', 'md-1', 'lg-1']}
>
// Sizing
<Box width={['100%', '50%', '33.33%']}>
// Order
<Box order={[1, 'unset']}>
Why: Array syntax automatically applies responsive breakpoints from the theme.
3. Theme Tokens for Spacing
Use theme spacing tokens instead of hardcoded values.
✅ Good:
// app/spaces/checkout/modules/single-page/CheckoutFooter.tsx:34
import { Box } from '@/libs/zest';
<Box
padding={isMobile ? 'global.sm-2' : 'global.md-2'}
pt={'zero'}
backgroundColor="neutral.100"
>
{/* Content */}
</Box>
Spacing token patterns:
// Longhand props <Box padding="md-1" margin="sm-2"> // Shorthand props <Box p="md-1" m="sm-2"> // Directional spacing <Box paddingX="sm-2" // horizontal padding paddingY="md-1" // vertical padding marginX="auto" // horizontal margin marginY="zero" // vertical margin > // Individual sides <Box pt="sm-2" // paddingTop pb="md-1" // paddingBottom mt="lg-1" // marginTop mb="zero" // marginBottom > // Global namespace <Box padding="global.sm-2" mt="global.md-1" columnGap="global.lg-1" >
Common spacing values:
- •
zero,xxs,xs,sm-1,sm-2,md-1,md-2,lg-1,lg-2 - •Prefix with
global.for global spacing tokens
Why: Theme tokens ensure consistency and enable theme switching.
4. Theme Tokens for Colors
Use theme color tokens for all colors.
✅ Good:
// app/operations/state-definitions/use-product-details-state/OperationPlayground.tsx:62
<Box
backgroundColor="error.600"
color="primary.100"
>
<Text color="primary.100" type="body-md-bold">
Error: {error}
</Text>
</Box>
Color token patterns:
// Neutral colors
<Box color="neutral.800" backgroundColor="neutral.100">
// Primary colors
<Text color="primary.600">
<Link linkColor="primary.600">
// Error/semantic colors
<Box backgroundColor="error.600">
<Text error> {/* Boolean shorthand for error color */}
Why: Color tokens maintain design consistency and support theme modes.
5. Text Component
Use Text component for typography with type variants.
✅ Good:
// app/operations/state-definitions/use-product-details-state/OperationPlayground.tsx:69
import { Text } from '@/libs/zest';
<Text color="primary.100" type="body-md-bold">
Error: {error}
</Text>
<Text as="h1">useProductDetailsState</Text>
<Text type="body-md-regular">
Use this playground to test the state and actions
</Text>
Text props:
// Typography variant <Text type="body-md-regular"> <Text type="body-md-bold"> // Semantic HTML <Text as="h1">Heading</Text> <Text as="h2">Subheading</Text> <Text as="p">Paragraph</Text> // Color <Text color="neutral.800"> // Error state <Text error>Error message</Text> // Spacing <Text mt="md-1" mb="sm-2">
Why: Text component provides consistent typography and semantic HTML.
6. Button Components
Use Button.Primary and IconButton.Primary compound components.
✅ Good:
// app/spaces/checkout/modules/single-page/CheckoutFooter.tsx:48
import { Button } from '@/libs/zest';
<Button.Primary
id="upm-playground.btn.submit"
data-test-id="upm-playground.btn.submit"
disabled={isDisabled}
type="submit"
form={ADDRESS_FORM_ID}
onClick={onClickButton}
loading={isPlacingOrder}
>
{CTALabel}
</Button.Primary>
Icon and IconButton pattern:
// app/spaces/whitelabel/modules/whitelabel-web/packages/libraries/toasts/Toast.tsx
import { Box, Icon, Text } from '@/libs/zest';
import { CloseOutline24, CircleCheckmarkFilled24 } from '@/libs/zest-support/icons/generated/24';
// Define icon colors with theme tokens
const toastIconColor: Record<ToastTypes, Color> = {
success: 'success.600',
error: 'error.700',
};
// Usage
<Box display="flex" gap="xs">
<Icon icon={toastIcon[type]} color={toastIconColor[type]} />
<Text color={toastTextColor[type]}>{text}</Text>
</Box>
<Icon icon={<CloseOutline24 />} color="neutral.700" />
Button props:
- •
disabled- Boolean to disable button - •
type- HTML button type ("submit", "button", "reset") - •
form- Form ID for submit buttons - •
onClick- Click handler - •
loading- Show loading spinner - •
id,data-test-id- Test identifiers
IconButton props:
- •
size- "sm", "md", "lg" - •
appearance- "negative", etc. - •
icon- React element for icon - •
onClick- Click handler
Why: Compound components provide variant-specific styling with type safety.
7. Form Components
Use Zest form components for inputs.
✅ Good:
// app/spaces/whitelabel/modules/whitelabel-web/packages/libraries/payment/src/components/AdyenIdealForm/index.tsx
import { Box, Checkbox, Text, useTheme } from '@/libs/zest';
const theme = useTheme();
const borderColor = theme.colors.primary['600'];
<Box
mb="sm-1"
__dangerouslySetCustomCSS={{
input: {
marginRight: 'sm-1',
},
}}
>
<Checkbox
onChange={onChange}
checked={!!values.idealTermsAndConditions}
label={agreementText}
/>
</Box>
TextArea pattern:
import { TextArea } from '@/libs/zest';
<TextArea
rows={30}
aria-label="Description"
defaultValue={input}
onChange={(e) => setInput(e.target.value)}
/>
Why: Form components provide consistent styling and accessibility.
Advanced Patterns
Flexbox Layout
// app/unified-spaces/registration-page/steps/index.tsx:122
<Box
display="flex"
flexDirection={['column', 'row']}
alignItems="center"
justifyContent="center"
flexBasis={['100%', '50%']}
maxWidth={['100%', '50%']}
>
{/* Content */}
</Box>
Positioning
// app/operations/state-definitions/use-product-details-state/OperationPlayground.tsx:59
<Box position="absolute" bottom="sm-1" left="sm-1">
{/* Positioned content */}
</Box>
Conditional Styling
// app/spaces/checkout/modules/single-page/CheckoutFooter.tsx:36
<Box
padding={isMobile ? 'global.sm-2' : 'global.md-2'}
opacity={!isMobile && isDisabled ? 0.6 : 1}
>
{/* Content */}
</Box>
Custom CSS Escape Hatch
// app/spaces/whitelabel/modules/whitelabel-web/packages/pages/my-deliveries/menus/EditMenuCollections/components/EmptyMenu.tsx:29
<Box
// eslint-disable-next-line no-restricted-syntax
__dangerouslySetCustomCSS={{
animation: fadeInBoxAnimation,
}}
>
{/* Content */}
</Box>
Why: Use only when Zest props are insufficient. Prefer Zest props or styled-components.
Mixing with Styled Components
// app/spaces/whitelabel/modules/whitelabel-web/packages/pages/my-deliveries/menus/EditMenuCollections/components/EmptyMenu.tsx:92
import { Box, Text } from '@/libs/zest';
import styled from 'styled-components';
const EmptyBox = styled.div`
height: 408px;
width: 100%;
border-radius: ${({ theme }) => theme.radii['border-radius-md']};
`;
export const EmptyMenu: React.FC = () => (
<Box display="flex" flexDirection="column">
<EmptyBox>
<Box
display="flex"
flexDirection="column"
justifyContent="center"
alignItems="center"
height="100%"
>
<Text mt="md-1">No meals selected</Text>
</Box>
</EmptyBox>
</Box>
);
Why: Use Box for layout/spacing, styled-components for custom visual styling.
Available Components
Layout Components
- •
Box- Foundational layout component with all styling props - •
Divider- Visual divider/separator - •
Card- Card container component
Typography
- •
Text- Typography component with variants
Buttons
- •
Button.Primary- Primary button variant - •
IconButton.Primary- Icon button variant
Forms
- •
Checkbox- Checkbox input with label - •
TextArea- Multi-line text input - •
Switch- Toggle switch - •
Item- List item / form item
Navigation
- •
Link- Navigation link component
Feedback
- •
Spinner- Loading spinner - •
Drawer- Drawer/modal component
Data Display
- •
Carousel- Carousel component - •
Tag- Tag/badge component - •
Icon- Icon wrapper
File Organization
components/
└── MyComponent/
├── MyComponent.tsx # Use Zest components
├── MyComponent.styles.ts # styled-components if needed
└── index.ts # Exports
Common Mistakes
- •Hardcoding spacing values - Use theme tokens:
padding="md-1"notpadding="16px" - •Not using responsive arrays - Use
width={['100%', '50%']}for responsive sizing - •Hardcoding colors - Use theme tokens:
color="neutral.800"notcolor="#333" - •Using div instead of Box - Box provides theme-aware props
- •Not using shorthand props - Use
p,mt,mbinstead of full names when convenient - •Mixing spacing systems - Use Zest spacing OR styled-components, not both
- •Forgetting global namespace - Some tokens need
global.prefix
Quick Reference
Basic Layout
import { Box } from '@/libs/zest';
<Box
display="flex"
flexDirection="column"
padding="md-1"
margin="sm-2"
backgroundColor="neutral.100"
>
{/* Content */}
</Box>
Responsive Layout
<Box
width={['100%', '50%', '33.33%']}
flexDirection={['column', 'row']}
padding={['sm-1', 'md-1', 'lg-1']}
display={['none', 'flex']}
>
{/* Content */}
</Box>
Typography
import { Text } from '@/libs/zest';
<Text type="body-md-bold" color="neutral.800">
Heading
</Text>
<Text as="p" type="body-md-regular">
Body text
</Text>
<Text error>Error message</Text>
Button
import { Button } from '@/libs/zest';
<Button.Primary
disabled={isDisabled}
loading={isLoading}
onClick={handleClick}
>
Submit
</Button.Primary>
IconButton
import { IconButton } from '@/libs/zest';
import { CloseOutline16 } from '@/libs/zest-support/icons/generated/16';
<IconButton.Primary
size="sm"
appearance="negative"
icon={<CloseOutline16 />}
onClick={handleClose}
/>
Form Components
import { Checkbox, TextArea } from '@/libs/zest';
<Checkbox
id="checkbox-id"
checked={isChecked}
onChange={handleChange}
label="Label text"
/>
<TextArea
rows={5}
aria-label="Description"
defaultValue={value}
onChange={(e) => setValue(e.target.value)}
/>
Spacing Shorthand
// Padding <Box p="md-1" pt="sm-2" pb="lg-1" px="md-2" py="sm-1"> // Margin <Box m="md-1" mt="sm-2" mb="lg-1" mx="auto" my="zero"> // Gap (for flex/grid) <Box columnGap="global.sm-1" rowGap="md-1">
Theme Token Reference
Spacing
- •
zero,xxs,xs,sm-1,sm-2,md-1,md-2,lg-1,lg-2 - •Prefix with
global.when needed:global.sm-2,global.md-1
Colors
- •Neutral:
neutral.100,neutral.200,neutral.300,neutral.800 - •Primary:
primary.100,primary.600 - •Error:
error.600
Breakpoints (Array Indices)
- •
[mobile, tablet, desktop] - •Example:
width={['100%', '50%', '33.33%']}