Skip to main content
Theme UI uses an array-based syntax for responsive styles, enabling mobile-first responsive design with minimal code.

Array-Based Syntax

Provide an array of values that map to breakpoints:
<Box
  sx={{
    fontSize: [1, 2, 3, 4]
  }}
/>
This creates responsive styles:
.element {
  font-size: 14px;  /* base */
}

@media screen and (min-width: 40em) {
  .element {
    font-size: 16px;
  }
}

@media screen and (min-width: 52em) {
  .element {
    font-size: 20px;
  }
}

@media screen and (min-width: 64em) {
  .element {
    font-size: 24px;
  }
}

Breakpoints

Define breakpoints in your theme:
const theme = {
  breakpoints: ['40em', '52em', '64em']
}
Default breakpoints from the source code:
export const defaultBreakpoints = [40, 52, 64].map((n) => n + 'em')
// ['40em', '52em', '64em']

Custom Breakpoints

You can use any CSS length unit:
const theme = {
  breakpoints: ['640px', '768px', '1024px', '1280px']
}
Or with custom media queries:
const theme = {
  breakpoints: [
    '@media screen and (min-width: 40em)',
    '@media screen and (min-width: 52em)',
    '@media screen and (min-width: 64em)'
  ]
}

Mobile-First Approach

The first value in the array is the base (mobile) style. Each subsequent value applies at the corresponding breakpoint:
<Box
  sx={{
    width: ['100%', '50%', '33.333%', '25%']
  }}
/>
Breakdown:
  • 100% - Mobile (default)
  • 50% - Tablet and up (≥ 40em)
  • 33.333% - Desktop and up (≥ 52em)
  • 25% - Large desktop (≥ 64em)

Responsive Implementation

Here’s how Theme UI transforms responsive arrays (from the source code):
const responsive =
  (styles: ThemeUIStyleObject) =>
  (theme?: Theme) => {
    const next = {}
    const breakpoints = (theme?.breakpoints) || defaultBreakpoints
    const mediaQueries = [
      null,
      ...breakpoints.map((n) =>
        n.includes('@media') ? n : `@media screen and (min-width: ${n})`
      ),
    ]

    for (const k in styles) {
      const key = k
      let value = styles[key]
      
      if (!Array.isArray(value)) {
        next[key] = value
        continue
      }
      
      for (let i = 0; i < value.slice(0, mediaQueries.length).length; i++) {
        const media = mediaQueries[i]
        if (!media) {
          next[key] = value[i]
          continue
        }
        next[media] = next[media] || {}
        if (value[i] == null) continue
        next[media][key] = value[i]
      }
    }
    return next
  }

Skipping Breakpoints

Use null or undefined to skip a breakpoint:
<Box
  sx={{
    width: ['100%', null, '50%']
  }}
/>
This applies:
  • 100% at mobile
  • No change at first breakpoint
  • 50% at second breakpoint

Multiple Responsive Properties

Combine multiple responsive properties:
<Box
  sx={{
    fontSize: [1, 2, 3],
    padding: [2, 3, 4],
    color: ['primary', 'secondary', 'accent'],
    display: ['block', 'flex']
  }}
/>

Responsive Typography

<Text
  sx={{
    fontSize: [2, 3, 4, 5],
    lineHeight: ['body', 'body', 'heading'],
    fontWeight: ['normal', 'normal', 'bold']
  }}
>
  Responsive heading
</Text>

Responsive Layout

Create responsive layouts:
<Grid
  sx={{
    gridTemplateColumns: ['1fr', '1fr 1fr', '1fr 1fr 1fr', 'repeat(4, 1fr)'],
    gap: [2, 3, 4]
  }}
>
  <Card>Item 1</Card>
  <Card>Item 2</Card>
  <Card>Item 3</Card>
  <Card>Item 4</Card>
</Grid>

Responsive Spacing

<Container
  sx={{
    px: [2, 3, 4, 5],
    py: [3, 4, 5, 6],
    maxWidth: ['100%', '768px', '1024px', '1280px']
  }}
>
  Content
</Container>

Nested Responsive Styles

Responsive arrays work in nested objects:
<Button
  sx={{
    fontSize: [1, 2],
    px: [2, 3],
    py: [1, 2],
    ':hover': {
      transform: ['scale(1.05)', 'scale(1.1)']
    }
  }}
>
  Hover me
</Button>

Responsive Variants

Combine responsive arrays with variants:
const theme = {
  buttons: {
    primary: {
      fontSize: [1, 2, 3],
      px: [3, 4, 5],
      py: [2, 2, 3]
    }
  }
}

Working with Grid and Flexbox

Responsive Flexbox

<Flex
  sx={{
    flexDirection: ['column', 'row'],
    alignItems: ['stretch', 'center'],
    justifyContent: ['flex-start', 'space-between'],
    gap: [2, 3, 4]
  }}
>
  <Box sx={{ flex: ['1', '1', '2'] }}>Main</Box>
  <Box sx={{ flex: 1 }}>Sidebar</Box>
</Flex>

Responsive Grid

<Grid
  sx={{
    gridTemplateColumns: [
      'repeat(1, 1fr)',
      'repeat(2, 1fr)',
      'repeat(3, 1fr)',
      'repeat(4, 1fr)'
    ],
    gap: [2, 3, 4]
  }}
>
  {items.map(item => <Card key={item.id}>{item.content}</Card>)}
</Grid>

Responsive Display

Show/hide elements at different breakpoints:
<Box>
  {/* Mobile menu */}
  <Box sx={{ display: ['block', 'none'] }}>
    <MobileMenu />
  </Box>
  
  {/* Desktop menu */}
  <Box sx={{ display: ['none', 'flex'] }}>
    <DesktopMenu />
  </Box>
</Box>

TypeScript Support

Responsive arrays are fully typed:
import type { ResponsiveStyleValue, ThemeUIStyleObject } from '@theme-ui/css'

// The ResponsiveStyleValue type
export type ResponsiveStyleValue<T> = 
  | T 
  | Array<T | null | undefined>

const styles: ThemeUIStyleObject = {
  fontSize: [1, 2, 3, 4],        // ResponsiveStyleValue<number>
  color: ['primary', 'secondary'], // ResponsiveStyleValue<string>
  display: ['none', 'block']      // ResponsiveStyleValue<string>
}

Best Practices

Start Mobile-First

Always define the mobile style first:
// Good
<Box sx={{ fontSize: [1, 2, 3] }} />

// Not ideal - requires null for mobile
<Box sx={{ fontSize: [null, 2, 3] }} />

Use Consistent Breakpoints

Define breakpoints that match your design system:
const theme = {
  breakpoints: [
    '640px',   // sm
    '768px',   // md
    '1024px',  // lg
    '1280px'   // xl
  ]
}

Combine with Theme Scales

Use theme values in responsive arrays:
<Box
  sx={{
    fontSize: [0, 1, 2, 3],  // Maps to theme.fontSizes
    p: [2, 3, 4, 5],         // Maps to theme.space
    color: ['text', 'primary'] // Maps to theme.colors
  }}
/>

Real-World Example

A complete responsive card component:
<Card
  sx={{
    // Layout
    display: 'flex',
    flexDirection: ['column', 'row'],
    
    // Spacing
    p: [3, 4, 5],
    gap: [2, 3, 4],
    
    // Typography
    fontSize: [1, 2],
    
    // Borders
    borderRadius: [1, 2],
    borderWidth: [1, 1, 2],
    
    // Shadows
    boxShadow: ['small', 'medium', 'large'],
    
    // Responsive image
    '& img': {
      width: ['100%', '200px', '300px'],
      height: ['200px', 'auto'],
      objectFit: ['cover', 'contain']
    },
    
    // Responsive hover
    ':hover': {
      transform: ['scale(1.02)', 'scale(1.05)'],
      boxShadow: ['medium', 'large', 'xlarge']
    }
  }}
>
  <img src="image.jpg" alt="Card" />
  <div>
    <h3>Card Title</h3>
    <p>Card content goes here.</p>
  </div>
</Card>
Responsive arrays provide a concise, mobile-first way to create responsive designs without writing media queries manually.

Build docs developers (and LLMs) love