Syntax Design System
Use this skill when working with Cambly's Syntax design system components in React applications. Invoke this skill when:
-
Implementing new UI features using Syntax components
-
Converting existing UI to use Syntax components
-
Fixing styling, accessibility, or component usage issues
-
Reviewing code that uses Syntax components
Core Principles
When working with Syntax components, you must:
-
Always use Box instead of plain divs for layout and containers
-
Never hardcode spacing or colors - use the design system's spacing scale (0-12) and color tokens
-
Always provide accessibilityLabel for IconButton components
-
Use semantic HTML via the as prop (e.g., <Box as="nav"> )
-
Add data-testid attributes for all interactive elements in tests
Installation
The Syntax packages required are:
-
@cambly/syntax-core
-
Main component library
-
@cambly/syntax-design-tokens
-
Design tokens
-
@cambly/syntax-icons
-
Icon components
-
@cambly/syntax-floating-components
-
Tooltips and popovers with Floating UI
npm install @cambly/syntax-core @cambly/syntax-design-tokens @cambly/syntax-icons @cambly/syntax-floating-components
Component Selection Guide
Use this decision tree to select the right component:
Layout & Containers:
-
Layout container → Box (with flexbox props)
-
Content container with elevation → Card
Buttons & Actions:
-
Button with text → Button
-
Button with icon only → IconButton (requires accessibilityLabel )
-
Link styled as button → LinkButton
-
Clickable area without button semantics → TapArea
-
Group related buttons → ButtonGroup
Typography:
-
Body text, labels, captions → Typography
-
Page/section headings → Heading (with proper as prop: h1-h6)
Form Inputs:
-
Single-line text → TextField
-
Multi-line text → TextArea
-
True/false selection → Checkbox
-
One of many options → RadioButton
-
Dropdown selection → SelectList (native) or RichSelectList (styled)
Feedback & Overlays:
-
Blocking dialog → Modal
-
Contextual overlay → Popover
-
Temporary notification → Toast
-
Hover information → Tooltip
Indicators & Status:
-
Small status indicator → Badge
-
User profile image → Avatar or AvatarGroup
-
Filter/tag → Chip
-
Visual separator → Divider
-
Icons → Icon (use with @cambly/syntax-icons )
Navigation:
- Tab interface → Tabs with TabButton or TabLink
Required Patterns
Box Component - The Foundation
Basic Layout:
<Box display="flex" direction="column" gap={4} justifyContent="center" alignItems="start"
{children} </Box>
Responsive Layout (mobile-first):
<Box direction="column" // Mobile: stack vertically smDirection="row" // Tablet (480px+): horizontal lgDirection="row" // Desktop (960px+): horizontal padding={2} smPadding={4} lgPadding={6}
{children} </Box>
Spacing (0-12 scale):
<Box margin={4} // All sides marginTop={2} // Individual sides marginStart={3} // Start (left in LTR, right in RTL) padding={4} paddingX={6} // Horizontal paddingY={2} // Vertical
Semantic HTML:
<Box as="section" role="main"> // Renders as <section> <Box as="nav"> // Renders as <nav>
Size Variants
Buttons/IconButtons/Chips: "sm" | "md" | "lg" (heights: 32px, 48px, 64px)
<Button text="Click me" size="md" />
Typography/Heading: 100 | 200 | 300 | 400 | 500 | 700 | 800 | 900 | 1100
<Typography size={400}>Body text</Typography> <Heading size={600}>Page title</Heading>
Icon: 100 | 200 | 300 | 400 | 500 | 600 | 700 | 800 | 900 | 1000
<Icon path={iconPath} size={400} />
Color System
Button Colors:
-
primary
-
Main call-to-action (blue)
-
secondary
-
Secondary actions (gray)
-
tertiary
-
Tertiary actions (minimal)
-
destructive-primary/secondary/tertiary
-
Dangerous actions (red)
-
success-primary/secondary/tertiary
-
Positive actions (green)
-
branded
-
Brand-specific color
<Button text="Delete" color="destructive-primary" /> <Button text="Cancel" color="secondary" />
Background Context (on prop):
<Button text="Click" on="darkBackground" /> <TextField label="Name" on="lightBackground" />
Universal Colors (Box, Icon, Typography):
-
Primary: primary , primary100 -primary900
-
Semantic: success100 -success900 , destructive100 -destructive900
-
Neutrals: gray10 -gray900 , white , black
-
Brand: sky , navy , teal , lilac , pink , cream
<Box backgroundColor="gray10" /> <Typography color="primary700">Text</Typography> <Icon color="success500" size={300} />
Form Patterns
TextField with validation:
<TextField label="Email" value={email} onChange={(e) => setEmail(e.target.value)} error={!!emailError} errorText={emailError} helperText="We'll never share your email" disabled={isSubmitting} data-testid="email-input" />
Checkbox:
<Checkbox label="I agree to terms" checked={agreed} onChange={(e) => setAgreed(e.target.checked)} error={submitted && !agreed} size="md" />
RadioButton group:
{ options.map((option) => ( <RadioButton key={option.value} label={option.label} checked={selected === option.value} onChange={() => setSelected(option.value)} name="options" /> )); }
Button Patterns
Button with icons:
import { ChevronRightIcon } from "@cambly/syntax-icons";
<Button text="Continue" endIcon={ChevronRightIcon} color="primary" size="lg" />;
Loading state:
<Button text="Save" loading={isSubmitting} loadingText="Saving..." disabled={!isDirty} />
IconButton (REQUIRED: accessibilityLabel):
import { CloseIcon } from "@cambly/syntax-icons";
<IconButton icon={CloseIcon} accessibilityLabel="Close dialog" // REQUIRED! color="secondary" size="sm" onClick={handleClose} />;
Typography Patterns
// Headings - always use semantic 'as' prop <Heading as="h1" size={800}>Page Title</Heading> <Heading as="h2" size={600}>Section Heading</Heading>
// Body text <Typography size={400}>Regular paragraph text</Typography> <Typography size={300} color="gray700">Secondary text</Typography>
// Text styling <Typography weight="semiBold" transform="uppercase" align="center" lineClamp={2} // Truncate after 2 lines
Content </Typography>
Modal & Dialog Pattern
<Modal isOpen={isOpen} onDismiss={handleClose} title="Confirm Action" size="md"> <Box padding={6}> <Typography>Are you sure?</Typography> <Box display="flex" gap={2} marginTop={4}> <Button text="Cancel" color="secondary" onClick={handleClose} /> <Button text="Confirm" color="primary" onClick={handleConfirm} /> </Box> </Box> </Modal>
Tooltip Pattern
<Tooltip text="Additional information"> <IconButton icon={InfoIcon} accessibilityLabel="Info" /> </Tooltip>
Card Pattern
<Card size="medium" backgroundColor="white"> <Box padding={4}> <Heading size={500}>Card Title</Heading> <Typography size={300} color="gray700"> Card content goes here </Typography> </Box> </Card>
Accessibility Requirements
Required Props
Form inputs need labels:
<TextField label="Email" /> // Good <Checkbox label="I agree" /> // Good
Icon buttons need accessibilityLabel:
<IconButton icon={TrashIcon} accessibilityLabel="Delete item" // Required for screen readers />
Use semantic HTML:
<Box as="nav"> // Semantic elements <Box as="main"> <Heading as="h1"> // Proper heading hierarchy
Focus Management
-
Components automatically handle keyboard focus visibility
-
Focus outlines only appear on keyboard navigation (not clicks)
-
All interactive elements are keyboard accessible
Form Accessibility
// Associate helper text with input <Box> <TextField label="Password" aria-describedby="password-help" /> <Typography id="password-help" size={200}> Must be at least 8 characters </Typography> </Box>
Common Mistakes to Avoid
❌ Using plain divs for layout
// BAD <div style={{ display: 'flex', gap: '16px' }}>
✅ Use Box component
// GOOD <Box display="flex" gap={4}>
❌ Missing accessibilityLabel
// BAD - inaccessible <IconButton icon={CloseIcon} />
✅ Always include accessibilityLabel
// GOOD <IconButton icon={CloseIcon} accessibilityLabel="Close" />
❌ Hardcoded pixel values
// BAD <Box style={{ marginTop: '24px' }}>
✅ Use spacing scale
// GOOD - uses 0-12 scale (6 = 24px) <Box marginTop={6}>
❌ Wrong icon imports
// BAD import CloseIcon from "@cambly/syntax-icons/CloseIcon";
✅ Named imports from package root
// GOOD import { CloseIcon } from "@cambly/syntax-icons";
❌ Ignoring background context
// BAD - button on dark background with wrong styling <Box backgroundColor="navy"> <Button text="Click me" /> </Box>
✅ Use 'on' prop for contrast
// GOOD <Box backgroundColor="navy"> <Button text="Click me" on="darkBackground" /> </Box>
❌ Avoid “self-spacing” children
// BAD <Box marginBottom={2}>Some text</Box> <Box marginBottom={2}>Some more text</Box>
✅ Use a parent wrapper to define sibling spacing
// GOOD <Box display="flex" direction="column" gap={2}> <Box>Some text</Box> <Box>Some more text</Box> </Box>
#### When child margins are OK
Use margins on a component only when it is intrinsic to that component, not dependent on neighbors (e.g., internal spacing within the component, or a visual affordance that always exists regardless of placement).
## Import Patterns
```typescript
// Core components
import { Box, Button, Typography, TextField } from "@cambly/syntax-core";
// Icons (named imports only)
import { ChevronRightIcon, CloseIcon, CheckIcon } from "@cambly/syntax-icons";
// Floating components
import { FloatingTooltip } from "@cambly/syntax-floating-components";
// Design tokens (rarely needed directly)
import { tokens } from "@cambly/syntax-design-tokens";
Testing
Always add data-testid
for testable elements:
<Button
text="Submit"
data-testid="submit-button"
onClick={handleSubmit}
/>
<TextField
label="Email"
data-testid="email-input"
value={email}
/>
Theme Provider
Wrap your app root with ThemeProvider:
import { ThemeProvider } from "@cambly/syntax-core";
function App() {
return <ThemeProvider>{/* Your app */}</ThemeProvider>;
}
Review Checklist
When reviewing or writing code with Syntax components, verify:
- Using Box
instead of plain divs for layout
- Using spacing scale (0-12) instead of hardcoded pixels
- All IconButton
components have accessibilityLabel
- Form inputs have labels
- Using semantic HTML via as
prop where appropriate
- Icons imported correctly from @cambly/syntax-icons
- on
prop used for components on dark backgrounds
- data-testid
added to interactive elements
- Proper heading hierarchy with Heading
component
- Responsive props used for mobile-first design
Additional Resources
- Storybook documentation: Run pnpm start
in the syntax repo
- Component props: TypeScript types provide full documentation
- All components have full TypeScript type definitions