Stack

Stack, HStack, and VStack are higher-level layout components that provide an ergonomic API for building one-dimensional layouts with consistent spacing between elements.

GitHubStorybook
yarn add @workday/canvas-kit-react
Sources
GitHubStorybook
Install
yarn add @workday/canvas-kit-react

Usage Guidance

Coming soon!

Examples

Stack is a higher-level layout component, meaning:

  1. It's built on top of lower-level components (Box and Flex)
  2. It's opinionated about how it builds layouts.

Stack has three core opinions that guide its API:

  • Space should be equal between child elements
  • Space should only exist between child elements
  • Children should be semantic elements

Much of layout design is founded on these opinions. Whether its creating lists of navigation links, groups of cards, or collections of buttons, once you think in stacks, you'll see them everywhere.

Basic Example

Stack is a composable component that's helpful for creating layouts with equal spacing between elements. It can create horizontal or vertical stacks of elements.

The most important distinction to make with Stack is that it is not a grid. Grid components are able to align items along two dimensions (rows and columns), and Stack is only built to support one dimension (rows or columns). That said, you can nest horizontal and vertical Stack components to create grid-like layouts.

Stack uses CSS pseudo-classes to apply margin to child elements. This makes it really simple to get consistent spacing between child elements without much manual effort. Below is a basic example. Note that Stack does not add any space above or below the child elements. This helps your element fit nicely within their containers.

One
Two
Three

Use this approach when:

  • You want consistent space between child elements
  • You don't need to apply custom margin to child elements
    • Specifically marginLeft/marginInlineStart for horizontal stacks and marginTop for vertical stacks
  • You don't want to add extra markup to wrap child elements

Setting Direction

The direction of the child elements is set with the flexDirection prop. This prop supports four directions: column, column-reverse, row, and row-reverse. By default, it will set the direction to row as is consistent with the CSS Flexbox spec.

Note: flex-direction and other CSS Flexbox attributes support bidirectionality (LTR and RTL) automatically. You don't need to think about switching directions for localization.

Note: spacing also supports bidirectionality automatically. This means you don't need to think about it for localization as long as you properly pass the direction to the theme object un

Setting Space

The space between child elements is set with the spacing prop, which supports all space token and string values. Stack intentionally only sets space between elements and never outside. For example: a vertical stack of three elements would only have margin added to the top of the second and third child elements. Keeping space between elements makes building layouts more block-like and predictable.

Managing Child Elements

There are three ways to manage space between child elements with Stack.

  • Implicit spacing applied directly to child elements (default behavior)
  • Implicit wrapping child elements with shouldWrapChildren
  • Explicit wrapping child elements with Stack.Items.

Each approach has its own benefits and limitations, which are discussed in further detail below. We chose to implicitly apply spacing to child elements as the default behavior because it requires the least overhead to implement and should support most use cases. The other approaches act as escape hatches for when the default behavior becomes forced. Should you find none of these approaches work for your use case, we recommend using Flex instead of Stack, which will provide you with more flexibility.

Valid Children

Because Stack applies styles to children, it requires its immediate children to be valid child elements. For example, text outside of an HTML tag would not render if it was an immediate Stack child. Here's an example snippet:

const ValidChildrenExample = () => {
return (
<Stack flexDirection="row" spacing="s">
This text will not render. Don't do this.
<p>This text will render.</p>
<span>This text will also render.</span>
</Stack>
);
};

Grid-like Layouts

You can nest vertical and horizontal Stacks to create grid-like layouts. Here are three horizontal Stacks nested within a vertical Stack. The fourth row is a single Flex component.

1
2
3
4
5
6
7
8
9

Small Containers

You can also use Stack to manage smaller layouts, such as within Cards.

Stack

Stack provides a simple API for managing consistent space between elements in one-dimensional layouts. In this Card example, we are setting extra-small spacing the heading, body text, and button elements.

HStack

HStack works identically to Stack but is limited to only row and row-reverse directions. It defaults to row if no flexDirection is provided.

Note: HStack supports bidirectionality automatically. This means that you won't need to switch the direction or spacing for localization. The direction switching is handled by the CSS Flexbox spec, and the spacing will flip direction based on the direction provided in the theme object.

In this example, HStack is placing three cards in a row with small spacing between them.

Diavola

sauce, smoked mozzarella, pepperoni, basil, chili flake

Four Cheese

mozzarella, gorgonzola, smoked mozzarella, parmesan, garlic oil

Veggie

sauce, mozzarella, artichokes, roasted red peppers, broccolini

VStack

VStack works identically to Stack but is limited to only column and column-reverse directions. It defaults to column if no flexDirection is provided.

In this example, VStack is placing three cards in a column with small spacing between them.

Diavola

sauce, smoked mozzarella, pepperoni, basil, chili flake

Four Cheese

mozzarella, gorgonzola, smoked mozzarella, parmesan, garlic oil

Veggie

sauce, mozzarella, artichokes, roasted red peppers, broccolini

shouldWrapChildren and Stack.Item

A side-effect of Stack's pseudo-classes is an increased style specificity. This specificity can override the child element's margin styles. If that creates a problem, there are two recommended options available:

  • Use the shouldWrapChildren prop (implicit option)
  • Use Stack.Item (explicit option)

shouldWrapChildren

The shouldWrapChildren prop will implicitly wrap each child in a Stack.Item component (which is a Box) and apply the margin to it instead of the child element provided. This allows you to control the margin of the child elements you provide and avoiding style collisions. The trade-off is that you do not have direct access to the Stack.Item being rendered. If having access to that element is necessary, using Stack.Item explicitly (as described below) could be the best solution.

The example below is a bit contrived but has enough information to convey the point. In this scenario, we have a horizontal stack of SecondaryButtons that render an icon, but they are visually divided into separate groupings. The navigational buttons are on the left, and the additional actions are spaced slightly further to the right. You could use two separate horizontal stacks here to achieve the same result, but you could also use shouldWrapChildren and then put additional margin only where needed. Either approach would be fine; it's a matter of personal preference.

Use this approach when:

  • You want consistent space between child elements
  • You need to apply custom margin to particular children
  • Wrapping each child element won't create issues with other semantic elements
  • You don't want to explicitly add extra markup to wrap child elements

Stack.Item

Sometimes inserting implicit wrapper elements can be problematic when you need access to those elements. In those situations, shouldWrapChildren is not the best choice, and you should opt for wrapping children in Stack.Items. A Stack.Item is a Box with some preset styles which can be overridden if needed.

In the example below, we wanted to keep our li elements as direct children of the ul stack. So we're wrapping each of the links with Stack.Items, casting them as lis and applying custom styles to each. This would not be possible with shouldWrapChildren.

Use this approach when:

  • You want consistent space between child elements
  • You need to have direct access to Stack.Item wrappers
  • Wrapping each child element won't create issues with other semantic elements

Stack Props

NameTypeDefaultDescription
spacing*number | (string & {}) | keyof CanvasSpacesets space values between child elements (bidirectional support)
shouldWrapChildrenboolean | undefinedfalsewhen `true` wraps each child element in a `Stack.Item`
refReact.Ref<any>Optional ref. If the component represents an element, this ref will be a reference to the real DOM element of the component. If `as` is set to an element, it will be that element. If `as` is a component, the reference will be to that component (or element if the component uses `React.forwardRef`).
as"symbol" | "object" | "small" | "a" | "abbr" | "address" | "area" | "article" | "aside" | "audio" | "b" | "base" | "bdi" | "bdo" | "big" | "blockquote" | "body" | "br" | "button" | ... 156 more ... | React.ComponentType<any>Optional override of the default element used by the component. Any valid tag or Component. If you provided a Component, this component should forward the ref using `React.forwardRef` and spread extra props to a root element.

Can't Find What You Need?

Check out our FAQ section which may help you find the information you're looking for.

FAQ Section