static/app/components/core/layout/container.mdx
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 === '2xs'; 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.
The simplest usage renders a div element with enhanced layout properties:
<Container padding="md" background="primary">
Basic container content
</Container>
The Container implements composition via <a href="/stories/layout/composition">render prop</a> pattern.
<Container padding="md" background="primary">
{props => <div {...props}>Basic container content</div>}
</Container>
as propThe Container component renders a div element by default, but you can specify the DOM node to render by passing a as prop.
<Container as="section" padding="md" background="primary">
Basic container content
</Container>
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>
<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>
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>
<Container background="primary" padding="md" radius="md">
Rounded corners
</Container>
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>
<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>
<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>
The 2xs breakpoint is a breakpoint that applies from 0 to the smallest defined breakpoint size.
<Storybook.Demo> <Container display={{'screen:2xs': 'none', 'screen:sm': 'block'}}> This content is hidden below the sm breakpoint </Container> </Storybook.Demo>
<Container display={{'screen:2xs': 'none', 'screen:sm': 'block'}}>
This content is hidden below the sm breakpoint
</Container>
Responsive props come in two flavors, keyed by breakpoint:
{xs: …}) resolve against the nearest query container —
the available space a component sits in. Container queries are the default, so
they need no prefix.screen:-prefixed keys ({'screen:xs': …}) resolve against the
viewport (@media).This matters because a panel in a narrow sidebar and the same panel full-width
have the same viewport but very different available widths. An element can never
query its own size, so declare a parent as a container with
containerType="inline-size", then use bare breakpoint keys on its descendants:
For background on 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.
<Storybook.SizingWindow display="block"> <Container containerType="inline-size" padding="md" background="secondary" radius="md" border="primary"
<Flex direction={{'2xs': 'column', xs: 'row'}} gap="md">
<Container padding="sm" background="primary" border="primary" radius="md">
In the 2xs range (container narrower than 500px) these stack...
</Container>
<Container padding="sm" background="primary" border="primary" radius="md">
...and switch to a row at xs and up (500px+). Drag the box narrower (handle at the
bottom-right) to flip it — independent of the viewport width.
</Container>
</Flex>
The two can be combined on one prop — e.g. direction={{xs: 'column', 'screen:lg': 'row'}}
is a column until its container reaches xs, then a row once the viewport reaches lg.
[!TIP] Prefer
containerType="inline-size"— it only contains the inline (width) axis, so height still flows from content.sizealso contains the block axis, so the element then needs an explicit height or its content collapses; only use it when you need height-based container queries.
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, updating as the container crosses a
breakpoint. Drag the box below across the xs breakpoint (500px) to see the
label swap — independent of the viewport width.
<Storybook.SizingWindow display="block"> <ContainerBreakpointExample /> </Storybook.SizingWindow>
// Rendered inside a query container, e.g.
// <Container containerType="inline-size">…</Container>
function PanelLabel() {
const breakpoint = useContainerBreakpoint();
const isCompact = breakpoint === '2xs';
return isCompact ? 'Compact label' : 'Full, descriptive label';
}
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>