Back to Sentry

Container

static/app/components/core/layout/container.mdx

26.7.110.8 KB
Original Source

import {Container, Flex, useContainerBreakpoint} from '@sentry/scraps/layout'; import {Text} from '@sentry/scraps/text'; import * as Storybook from 'sentry/stories';

export const documentation = import('!!type-loader!@sentry/scraps/layout/container');

import {SIZES} from './stories/consts';

export function ContainerBreakpointLabel() { const breakpoint = useContainerBreakpoint(); const isCompact = breakpoint === 'zero'; return ( <span> Active container breakpoint: <Text bold>{breakpoint}</Text> —{' '} {isCompact ? 'showing a compact label.' : 'showing the full, descriptive label.'} </span> ); }

export function ContainerBreakpointExample() { return ( <Container containerType="inline-size" padding="md" background="secondary" border="primary" radius="md" > <ContainerBreakpointLabel /> </Container> ); }

The Container component is a foundational layout component that extends HTML elements with responsive CSS properties through props. It serves as the building block for other layout components like Flex and Grid to provides a consistent API for spacing, sizing, positioning, and styling.

You can specialize the Container component yourself and tailor it to your use case if neither Grid nor Flex are sufficient, but that should be an exception and you should try to use the higher order components instead.

Usage

The simplest usage renders a div element with enhanced layout properties:

jsx
<Container padding="md" background="primary">
  Basic container content
</Container>

Composition

The Container implements composition via <a href="/stories/layout/composition">render prop</a> pattern.

tsx
<Container padding="md" background="primary">
  {props => <div {...props}>Basic container content</div>}
</Container>

Specifying the DOM Node via as prop

The Container component renders a div element by default, but you can specify the DOM node to render by passing a as prop.

tsx
<Container as="section" padding="md" background="primary">
  Basic container content
</Container>

Spacing

The padding prop uses the theme's spacing system with shorthand support:

<Storybook.Demo> <Flex gap="md"> {SIZES.map(size => ( <Container key={size} background="primary" padding={size} border="primary" radius="md" > <strong>{size} padding</strong> -{' '} {size === 'xs' ? '4px' : size === 'sm' ? '6px' : size === 'md' ? '8px' : size === 'lg' ? '12px' : size === 'xl' ? '16px' : '24px'} </Container> ))} </Flex> </Storybook.Demo>

jsx
<Container padding="md">8px padding on all sides</Container>
<Container padding="md lg">8px top/bottom, 12px left/right</Container>
<Container padding="xs sm md lg">2px top, 6px right, 8px bottom, 12px left</Container>

Border Radius

Apply rounded corners using theme radius values:

<Storybook.Demo> <Flex gap="md"> <Container background="primary" padding="md" radius="sm" border="primary"> Small radius </Container> <Container background="primary" padding="md" radius="md" border="primary"> Medium radius </Container> <Container background="primary" padding="md" radius="lg" border="primary"> Large radius </Container> </Flex> </Storybook.Demo>

jsx
<Container background="primary" padding="md" radius="md">
  Rounded corners
</Container>

Position

Set positioning for absolute/relative layouts:

<Storybook.Demo> <Container position="relative" height="100px" background="primary" border="primary"> <Container position="absolute" padding="xs" background="primary" style={{top: '12px', right: '8px'}} border="primary" > Absolute positioned </Container> Relative container </Container> </Storybook.Demo>

jsx
<Container position="relative" height="100px" background="primary">
  <Container
    position="absolute"
    padding="xs"
    background="primary"
    style={{top: '12px', right: '8px'}}
  >
    Absolute positioned
  </Container>
  Relative container
</Container>

Responsive Example

<Storybook.Demo> <Flex padding={{'screen:xs': 'sm', 'screen:lg': 'xl'}} background={{ 'screen:xs': 'primary', 'screen:sm': 'secondary', 'screen:lg': 'primary', }} radius={{'screen:xs': '0', 'screen:sm': 'md'}} border="primary"

<Container padding="sm" background="primary" style={{flex: 1}} border="primary">
  Item 1
</Container>
<Container padding="sm" background="primary" style={{flex: 1}} border="primary">
  Item 2
</Container>
</Flex> </Storybook.Demo> ```jsx <Container // Padding increases at lg breakpoint padding={{'screen:xs': 'sm', 'screen:lg': 'xl'}} // Background changes at sm and lg breakpoints background={{'screen:xs': 'primary', 'screen:sm': 'secondary', 'screen:lg': 'primary'}} // Adds border="primary" radius at sm breakpoint radius={{'screen:xs': '0', 'screen:sm': 'md'}} > <div>Responsive layout content</div> </Container> ```

Container Queries

Responsive props take a single value (padding="md") or an object keyed by breakpoint (padding={{md: 'sm', lg: 'xl'}}), where each value applies from that breakpoint up. Keys come in two kinds:

  • bare keys follow the container — the room a component has where it sits.
  • screen: keys follow the viewport — the browser window.

Each kind has its own scale, and both can be mixed on one prop.

[!NOTE] Prefer container keys. A component should respond to the room it has, not the size of the window. Reach for screen: only when the layout truly depends on the viewport.

Container tokens

Resolve against the nearest query container (@container, theme.container). zero is the always-applied base.

TokenMin width
zero0px (base)
3xs320px
2xs384px
xs448px
sm512px
md576px
lg640px
xl768px
2xl896px
3xl1024px
4xl1152px
5xl1280px

An element can't query its own size — mark a parent with containerType="inline-size", then use bare keys on its descendants:

<Storybook.SizingWindow display="block"> <Container containerType="inline-size" padding="md" background="secondary" radius="md" border="primary"

<Flex direction={{zero: 'column', sm: 'row'}} gap="md">
  <Container padding="sm" background="primary" border="primary" radius="md">
    Stacked below `sm` (container narrower than 512px)...
  </Container>
  <Container padding="sm" background="primary" border="primary" radius="md">
    ...and side by side from `sm` up (512px+). Drag the handle at the bottom-right to
    flip it — the viewport never changes.
  </Container>
</Flex>
</Container> </Storybook.SizingWindow> ```jsx <Container containerType="inline-size"> <Flex direction={{zero: 'column', sm: 'row'}}> <div>Reflows based on the container's width, not the viewport's.</div> <div>Resize the container above to see it switch.</div> </Flex> </Container> ```

[!TIP] Prefer containerType="inline-size". It contains only the inline (width) axis, so height still flows from content. size also contains the block axis, which forces you to give the element an explicit height or its content collapses — only use it for height-based container queries.

For the underlying CSS, see MDN's <a href="https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_containment/Container_queries">container queries guide</a>, <a href="https://developer.mozilla.org/en-US/docs/Web/CSS/container-type">container-type</a>, and the <a href="https://developer.mozilla.org/en-US/docs/Web/CSS/@container">@container</a> at-rule.

Screen tokens

Resolve against the viewport (@media, theme.breakpoints). 2xs is the always-applied base.

TokenMin width
2xs0px (base)
xs500px
sm800px
md992px
lg1200px
xl1440px
2xl2560px

Mixing the two

Both kinds of key can live on the same prop. For example, direction={{zero: 'column', md: 'row', 'screen:lg': 'column'}} starts as a column, becomes a row once the container reaches md, then returns to a column once the viewport reaches lg.

Reading the breakpoint in JS

When you need the resolved breakpoint in JS rather than just CSS, use useContainerBreakpoint() — the JS equivalent of a CSS container query and the container-scoped replacement for width-based useMedia. Call it from a descendant of a query container (a Container/Flex/… with containerType) and it returns the container's active breakpoint (on the container scale — zero through 5xl), updating as the container crosses a breakpoint. Drag the box below across the 3xs breakpoint (320px) to see the label swap — independent of the viewport width.

<Storybook.SizingWindow display="block"> <ContainerBreakpointExample /> </Storybook.SizingWindow>

jsx
// Rendered inside a query container, e.g.
// <Container containerType="inline-size">…</Container>
function PanelLabel() {
  const breakpoint = useContainerBreakpoint();
  const isCompact = breakpoint === 'zero';
  return isCompact ? 'Compact label' : 'Full, descriptive label';
}

Grid Integration

The area prop supports CSS Grid integration:

<Storybook.Demo> <Container display="grid" style={{ gridTemplateAreas: '"header header" "sidebar content"', gridTemplateColumns: '200px 1fr', gridTemplateRows: 'auto 1fr', gap: '8px', height: '200px', }}

<Container
  area="header"
  background="primary"
  padding="sm"
  border="primary"
  radius="md"
>
  Header
</Container>
<Container
  area="sidebar"
  background="primary"
  padding="sm"
  border="primary"
  radius="md"
>
  Sidebar
</Container>
<Container
  area="content"
  background="primary"
  padding="sm"
  border="primary"
  radius="md"
>
  Content
</Container>
</Container> </Storybook.Demo> ```jsx <Container display="grid" style={{ gridTemplateAreas: '"header header" "sidebar content"', gridTemplateColumns: '200px 1fr', gridTemplateRows: 'auto 1fr', }} > <Container area="header" background="primary" padding="sm" radius="md"> Header </Container> <Container area="sidebar" background="primary" padding="sm" radius="md"> Sidebar </Container> <Container area="content" background="primary" padding="sm" radius="md"> Content </Container> </Container> ```