Layout

This page describes how to build application layouts with React Spectrum using Flex or Grid, as well as how to integrate with pre-defined Spectrum layouts using slots.

Introduction#


React Spectrum includes layout components to help build Spectrum compliant applications more easily. The Flex and Grid components are containers, which are responsible for the layout of their children. Flex follows the CSS flexbox algorithm, while Grid implements CSS grid. These components provide props with pre-defined Spectrum variables for sizing, spacing, and other layout options. You can use Flex and Grid together as needed to build different parts of your application, and even nest them to create more complex layouts.

In general, you should prefer using flex and grid over other CSS layout models. Spacing between components should be left to parent layout components rather than added as via margins to specific children. This helps ensure components are composable when reused in different places, and ensures that spacing is consistent.

In addition to Flex and Grid, some React Spectrum components include pre-built layouts which you can insert your content into via slots. This often happens automatically through the use of semantic elements, but can also be configured through props. For example, the Dialog component accepts Heading, Content, and Footer elements as children and will handle the layout automatically.

Flex#


The flexbox layout model is a simple yet versatile method of laying out components in rows and columns. You can use it to build vertical or horizontal stacks, simple wrapping grids, and more. The Flex component can be used to create flexbox containers, and any React Spectrum component can be used as a child. Flex layouts are also automatically mirrored in right-to-left languages, and can be nested to create more complex layouts.

In addition to the properties widely supported by CSS, React Spectrum also shims the gap property, along with rowGap and columnGap. These properties make it much easier to build layouts with consistent space between each item. The gap can be defined with Spectrum dimension variables to ensure consistency across applications, and allow the layout to adapt to different devices automatically.

Example#

This example shows a simple vertical stack, with a gap between each item defined using a Spectrum dimension variable.

<Flex direction="column" width="size-2000" gap="size-100">
  <View backgroundColor="celery-600" height="size-800" />
  <View backgroundColor="blue-600" height="size-800" />
  <View backgroundColor="magenta-600" height="size-800" />
</Flex>
<Flex direction="column" width="size-2000" gap="size-100">
  <View backgroundColor="celery-600" height="size-800" />
  <View backgroundColor="blue-600" height="size-800" />
  <View backgroundColor="magenta-600" height="size-800" />
</Flex>
<Flex
  direction="column"
  width="size-2000"
  gap="size-100">
  <View
    backgroundColor="celery-600"
    height="size-800"
  />
  <View
    backgroundColor="blue-600"
    height="size-800"
  />
  <View
    backgroundColor="magenta-600"
    height="size-800"
  />
</Flex>

Learn more#

You can learn more about Flex and see more examples on the Flex page. In addition, there are many great resources on the web for learning flexbox.

Grid#


CSS grid is a powerful way to lay out elements in two dimensions. It can be used to to build full page application layouts, or smaller user interface elements. It is especially powerful because it allows you to build many types of layouts without extra presentational elements, keeping your code clean and semantic. In addition, grid layouts are automatically mirrored in right-to-left languages.

The Grid component can be used as a container to define a grid layout. Any React Spectrum component can be used as a child of a Grid. The Grid component extends the CSS syntax to support defining grids using Spectrum-defined dimension values. This ensures that sizing and spacing is consistent between applications, and allows the layout to adapt to different devices automatically.

Defining grids#

There are many ways to define grids, but the simplest is to use the areas prop to declaratively specify your layout using named areas. This prop accepts an array of strings which represent rows. Within the rows, you specify space separated names for grid areas. The children of the Grid can declare the gridArea prop, which places them into these named regions.

In addition, you can define the columns and rows props on the Grid container to specify the widths and heights of the columns and rows respectively. This can be done using Spectrum dimension values to ensure they are adaptive on various devices.

The following example shows how you could use Grid to declare a common application layout, with a header, sidebar, content area, and footer. Notice how there are no nested layout elements — the layout is entirely declared in the Grid and the children simply declare where they should be placed.

<Grid
  areas={['header  header', 'sidebar content', 'footer  footer']}
  columns={['1fr', '3fr']}
  rows={['size-1000', 'auto', 'size-1000']}
  height="size-6000"
  gap="size-100">
  <View backgroundColor="celery-600" gridArea="header" />
  <View backgroundColor="blue-600" gridArea="sidebar" />
  <View backgroundColor="purple-600" gridArea="content" />
  <View backgroundColor="magenta-600" gridArea="footer" />
</Grid>
<Grid
  areas={[
    'header  header',
    'sidebar content',
    'footer  footer'
  ]}
  columns={['1fr', '3fr']}
  rows={['size-1000', 'auto', 'size-1000']}
  height="size-6000"
  gap="size-100">
  <View backgroundColor="celery-600" gridArea="header" />
  <View backgroundColor="blue-600" gridArea="sidebar" />
  <View backgroundColor="purple-600" gridArea="content" />
  <View backgroundColor="magenta-600" gridArea="footer" />
</Grid>
<Grid
  areas={[
    'header  header',
    'sidebar content',
    'footer  footer'
  ]}
  columns={[
    '1fr',
    '3fr'
  ]}
  rows={[
    'size-1000',
    'auto',
    'size-1000'
  ]}
  height="size-6000"
  gap="size-100">
  <View
    backgroundColor="celery-600"
    gridArea="header"
  />
  <View
    backgroundColor="blue-600"
    gridArea="sidebar"
  />
  <View
    backgroundColor="purple-600"
    gridArea="content"
  />
  <View
    backgroundColor="magenta-600"
    gridArea="footer"
  />
</Grid>

Learn more#

You can learn more about Grid and see more examples on the Grid page. In addition, there are many great resources on the web for learning CSS grid.

Responsive layout#


All props of the Flex and Grid components support object syntax to specify different values for the prop depending on the responsive breakpoint. By default, breakpoints are named following t-shirt sizing, and correspond to common device resolutions. Breakpoints can be overridden at the application level via the Provider component. In addition to breakpoints, a base value can be provided to style props, which will be applied when no breakpoints match.

This example applies Flex direction row by default, but switches to column at the large breakpoint.

<Flex direction={{base: 'row', L: 'column'}} />
<Flex direction={{base: 'row', L: 'column'}} />
<Flex
  direction={{
    base: 'row',
    L: 'column'
  }}
/>

React Spectrum's breakpoints are mobile first, which means style props apply at that breakpoint and above. For example, the L breakpoint is applied at screen sizes 1024px and wider. The base value should be used to specify the layout at the smallest possible screen size, and additional breakpoints may be added to adapt the layout for larger devices.

The following example shows how you could use Grid to define a responsive layout. It consists of a header, navigation area, content area, table of contents, and footer. On small screens, all of these areas are displayed in a vertical stack, and the table of contents is hidden. At the medium breakpoint, the navigation and content appear next to each other. At the large breakpoint, the table of contents appears.

This layout is accomplished by changing the grid areas and columns at the medium and large breakpoints. In addition, the isHidden prop of the table of contents element is set to true except at the large breakpoint. Within the navigation area, the boxes representing links are displayed in a vertical stack on large displays, and a horizontal stack on small screens. This is done by changing the Flex direction prop between row and column at the medium breakpoint.

Resize your browser window to see how the layout changes.

<Grid
  areas={{
    base: ['header', 'nav', 'content', 'footer'],
    M: [
      'header   header',
      'nav      content',
      'nav      content',
      'footer   footer'
    ],
    L: [
      'header header  header',
      'nav    content toc',
      'nav    content toc',
      'footer footer  footer'
    ]
  }}
  columns={{
    M: ['size-2000', '1fr'],
    L: ['size-2000', '1fr', 'size-2000']
  }}
  gap="size-100">
  <View backgroundColor="celery-600" gridArea="header" height="size-1000" />
  <View backgroundColor="blue-600" gridArea="nav">
    <Flex
      direction={{base: 'row', M: 'column'}}
      gap="size-100"
      margin="size-100">
      <View
        backgroundColor="static-gray-50"
        height="size-250"
        minWidth="size-900"
      />
      <View
        backgroundColor="static-gray-50"
        height="size-250"
        minWidth="size-900"
      />
      <View
        backgroundColor="static-gray-50"
        height="size-250"
        minWidth="size-900"
      />
    </Flex>
  </View>
  <View backgroundColor="purple-600" gridArea="content" height="size-4600" />
  <View
    backgroundColor="magenta-600"
    gridArea="toc"
    minHeight="size-1000"
    isHidden={{base: true, L: false}}
  />
  <View backgroundColor="seafoam-600" gridArea="footer" height="size-1000" />
</Grid>
<Grid
  areas={{
    base: ['header', 'nav', 'content', 'footer'],
    M: [
      'header   header',
      'nav      content',
      'nav      content',
      'footer   footer'
    ],
    L: [
      'header header  header',
      'nav    content toc',
      'nav    content toc',
      'footer footer  footer'
    ]
  }}
  columns={{
    M: ['size-2000', '1fr'],
    L: ['size-2000', '1fr', 'size-2000']
  }}
  gap="size-100">
  <View
    backgroundColor="celery-600"
    gridArea="header"
    height="size-1000"
  />
  <View backgroundColor="blue-600" gridArea="nav">
    <Flex
      direction={{base: 'row', M: 'column'}}
      gap="size-100"
      margin="size-100">
      <View
        backgroundColor="static-gray-50"
        height="size-250"
        minWidth="size-900"
      />
      <View
        backgroundColor="static-gray-50"
        height="size-250"
        minWidth="size-900"
      />
      <View
        backgroundColor="static-gray-50"
        height="size-250"
        minWidth="size-900"
      />
    </Flex>
  </View>
  <View
    backgroundColor="purple-600"
    gridArea="content"
    height="size-4600"
  />
  <View
    backgroundColor="magenta-600"
    gridArea="toc"
    minHeight="size-1000"
    isHidden={{base: true, L: false}}
  />
  <View
    backgroundColor="seafoam-600"
    gridArea="footer"
    height="size-1000"
  />
</Grid>
<Grid
  areas={{
    base: [
      'header',
      'nav',
      'content',
      'footer'
    ],
    M: [
      'header   header',
      'nav      content',
      'nav      content',
      'footer   footer'
    ],
    L: [
      'header header  header',
      'nav    content toc',
      'nav    content toc',
      'footer footer  footer'
    ]
  }}
  columns={{
    M: [
      'size-2000',
      '1fr'
    ],
    L: [
      'size-2000',
      '1fr',
      'size-2000'
    ]
  }}
  gap="size-100">
  <View
    backgroundColor="celery-600"
    gridArea="header"
    height="size-1000"
  />
  <View
    backgroundColor="blue-600"
    gridArea="nav">
    <Flex
      direction={{
        base: 'row',
        M: 'column'
      }}
      gap="size-100"
      margin="size-100">
      <View
        backgroundColor="static-gray-50"
        height="size-250"
        minWidth="size-900"
      />
      <View
        backgroundColor="static-gray-50"
        height="size-250"
        minWidth="size-900"
      />
      <View
        backgroundColor="static-gray-50"
        height="size-250"
        minWidth="size-900"
      />
    </Flex>
  </View>
  <View
    backgroundColor="purple-600"
    gridArea="content"
    height="size-4600"
  />
  <View
    backgroundColor="magenta-600"
    gridArea="toc"
    minHeight="size-1000"
    isHidden={{
      base: true,
      L: false
    }}
  />
  <View
    backgroundColor="seafoam-600"
    gridArea="footer"
    height="size-1000"
  />
</Grid>

Slots#


Several React Spectrum components include pre-defined layouts that you can insert elements into via slots. Slots are named areas in a component that receive children and provide style and layout for them. This often occurs automatically through the use of semantic elements like Header, Content, and Footer, which include default slots out of the box.

Slots and semantic elements allow you to let Spectrum define the complex layouts required by many components according to design requirements automatically. You just provide the proper child elements, and the container component handles the layout and styling of the children for you.

For example, the Dialog component accepts many types of children, e.g. a Heading for the title, a Content element for the body, a ButtonGroup for the buttons, etc., and Dialog takes care of positioning and styling them properly for the dialog context. In addition, the layout of these items can change depending on the screen size. For example, the buttons in full screen dialogs move from the top to the bottom on mobile.

Another example of this is the complex items supported by Picker and Menu. These components allow icons, text, and descriptions to be added as children of items, and the layout and styling is handled automatically. The icon and main text element of an item are placed in slots automatically, and the description text element is placed using the slot prop to specify a named area.

<Picker label="Permission" defaultSelectedKey="read">
  <Item textValue="Read" key="read">
    <Book size="S" />
    <Text>Read</Text>
    <Text slot="description">Read only</Text>
  </Item>
  <Item textValue="Write" key="write">
    <Draw size="S" />
    <Text>Write</Text>
    <Text slot="description">Read and write only</Text>
  </Item>
  <Item textValue="Admin" key="admin">
    <BulkEditUsers size="S" />
    <Text>Admin</Text>
    <Text slot="description">Full access</Text>
  </Item>
</Picker>
<Picker label="Permission" defaultSelectedKey="read">
  <Item textValue="Read" key="read">
    <Book size="S" />
    <Text>Read</Text>
    <Text slot="description">Read only</Text>
  </Item>
  <Item textValue="Write" key="write">
    <Draw size="S" />
    <Text>Write</Text>
    <Text slot="description">Read and write only</Text>
  </Item>
  <Item textValue="Admin" key="admin">
    <BulkEditUsers size="S" />
    <Text>Admin</Text>
    <Text slot="description">Full access</Text>
  </Item>
</Picker>
<Picker
  label="Permission"
  defaultSelectedKey="read">
  <Item
    textValue="Read"
    key="read">
    <Book size="S" />
    <Text>Read</Text>
    <Text slot="description">
      Read only
    </Text>
  </Item>
  <Item
    textValue="Write"
    key="write">
    <Draw size="S" />
    <Text>Write</Text>
    <Text slot="description">
      Read and write
      only
    </Text>
  </Item>
  <Item
    textValue="Admin"
    key="admin">
    <BulkEditUsers size="S" />
    <Text>Admin</Text>
    <Text slot="description">
      Full access
    </Text>
  </Item>
</Picker>

You can learn more about the slots and semantic elements supported by each component by reading their individual documentation pages.