Skip to main content

ColumnLayout

Places components side by side. Use it to show several items of the same importance in a row, like the SpaceCards of a home, or to make one item the focus with a smaller one beside it.

Loading…
Live editor
<ColumnLayout>
  <SensorReading metric="temperature" value={22.4} unit="celsius" size="md" />
  <SensorReading
    metric="humidity"
    value={48}
    unit="percent-rh"
    fractionDigits={0}
    size="md"
  />
  <SensorReading
    metric="co2"
    value={840}
    unit="ppm"
    fractionDigits={0}
    size="md"
  />
</ColumnLayout>
Try it

The examples on this page are live. Change the code and the result updates as you type.

ColumnLayout only adds columns and the space between them. It has no background, border or padding, so you never see it, and you can put it inside any other component.

Import​

import { ColumnLayout } from "@detriz-sh-kit/ui";

The component needs the kit's styles. Load them once, at the root of your app:

import "@detriz-sh-kit/tokens/tokens.css";
import "@detriz-sh-kit/ui/styles.css";

See Installation for how to install the packages.

Basic usage​

Put the components you want side by side inside it:

import { ColumnLayout, SpaceCard } from "@detriz-sh-kit/ui";

<ColumnLayout>
<SpaceCard name="Bedroom" readings={bedroomReadings} />
<SpaceCard name="Kitchen" readings={kitchenReadings} />
</ColumnLayout>;

The examples below leave out the import line. In your app, you need it.

When to use it​

Use ColumnLayout:

  • for a grid of cards of the same importance, like one SpaceCard per Space
  • for a main item with a smaller one beside it, with ratio="2:1" or "1:2"

Don't use it for:

  • Components one under the other. Use StackLayout. ColumnLayout only falls back to one column when it runs out of room.
  • A group with a heading. Use ContentSection, and put a ColumnLayout inside it.
  • Padding or a background. ColumnLayout adds neither. The screen or the card around it owns those.

Ratios​

Use the ratio prop. The default is equal.

ratioColumnsUse it when
equalAs many as fit, same widthItems of the same importance.
2:1Wide, then narrowThe first child is the focus, with a supporting item beside it.
1:2Narrow, then wideThe second child is the focus.

There are only three ratios and no gap prop, so every screen uses layouts that have been tested. The gap is always 16px, between columns and between rows.

equal​

Loading…
Live editor
<ColumnLayout ratio="equal">
  <SensorReading metric="temperature" value={22.4} unit="celsius" size="md" />
  <SensorReading
    metric="humidity"
    value={48}
    unit="percent-rh"
    fractionDigits={0}
    size="md"
  />
</ColumnLayout>

2:1​

Loading…
Live editor
<ColumnLayout ratio="2:1">
  <ModuleCard
    name="Air Quality Module"
    connectionStatus="connected"
    sensors={[
      { metric: "co2", value: 1250, unit: "ppm", fractionDigits: 0 },
      { metric: "voc", value: 120, unit: "ppb", fractionDigits: 0 },
    ]}
  />
  <ModuleCard
    name="Climate Module"
    connectionStatus="connected"
    sensors={[{ metric: "temperature", value: 22.4, unit: "celsius" }]}
  />
</ColumnLayout>

1:2​

The narrow child still comes first, so it is also read first.

Loading…
Live editor
<ColumnLayout ratio="1:2">
  <ModuleCard
    name="Climate Module"
    connectionStatus="connected"
    sensors={[{ metric: "temperature", value: 22.4, unit: "celsius" }]}
  />
  <ModuleCard
    name="Air Quality Module"
    connectionStatus="connected"
    sensors={[
      { metric: "co2", value: 1250, unit: "ppm", fractionDigits: 0 },
      { metric: "voc", value: 120, unit: "ppb", fractionDigits: 0 },
    ]}
  />
</ColumnLayout>

Responding to width​

ColumnLayout looks at its own width, not the screen's, so it works the same on a full page and inside another column. A column is never narrower than 192px, the narrowest width a ModuleCard is designed for.

ratioNarrowWide
equalBelow 400px: one columnOne more column for every 208px of width
2:1 and 1:2Below 592px: one columnFrom 592px: two columns

Drag the slider to change the width:

Loading…
Live editor
function ResizeColumns() {
  const [width, setWidth] = useState(640);
  const card = (name, value) => (
    <SpaceCard
      name={name}
      readings={[
        {
          metric: "temperature",
          value,
          unit: "celsius",
          connectionStatus: "connected",
        },
      ]}
    />
  );

  return (
    <StackLayout>
      <label>
        Width: {width}px{" "}
        <input
          type="range"
          min="200"
          max="760"
          value={width}
          onChange={(e) => setWidth(Number(e.target.value))}
        />
      </label>
      <div style={{ width }}>
        <ColumnLayout>
          {card("Bedroom", 21.5)}
          {card("Kitchen", 23.1)}
          {card("Hall", 19.8)}
        </ColumnLayout>
      </div>
    </StackLayout>
  );
}

Number of children​

Any number of children works:

Childrenequal2:1 and 1:2
OneFills the full widthFills the full width, with no empty column beside it
Fewer than fit a rowThey share the full width–
More than fit a rowWrap onto new rows, with the same column widthWrap onto new rows, each row with the same ratio

2:1 and 1:2 are made for two children.

Same height in a row​

Children in the same row get the same height, so the tops and bottoms of a row of cards line up. If the children have different heights on purpose, for example to compare sizes, put them in a StackLayout instead.

Loading…
Live editor
<ColumnLayout>
  <SpaceCard
    name="Bedroom"
    readings={[
      {
        metric: "temperature",
        value: 21.5,
        unit: "celsius",
        connectionStatus: "connected",
      },
      {
        metric: "humidity",
        value: 48,
        unit: "percent-rh",
        fractionDigits: 0,
        connectionStatus: "connected",
      },
    ]}
  />
  <SpaceCard
    name="Hall"
    readings={[
      {
        metric: "temperature",
        value: 19.8,
        unit: "celsius",
        connectionStatus: "connected",
      },
    ]}
  />
</ColumnLayout>

Props​

PropTypeDefaultDescription
childrenReactNode–Required. The components to place side by side, in reading order.
ratio"equal" | "2:1" | "1:2""equal"How the width is divided.

The types ColumnLayoutProps and ColumnLayoutRatio are exported too:

import type { ColumnLayoutProps, ColumnLayoutRatio } from "@detriz-sh-kit/ui";

Long text​

ColumnLayout has no text of its own. A child with long content can't push the columns wider than their parent: each child handles its own long text (see its page).

Loading…
Live editor
<div
  style={{ maxWidth: 240, outline: "1px dashed var(--shk-palette-gray-300)" }}
>
  <ColumnLayout>
    <ModuleCard
      name="Bedroom ceiling Air Quality Module near the window"
      connectionStatus="connected"
      sensors={[{ metric: "co2", value: 840, unit: "ppm", fractionDigits: 0 }]}
    />
  </ColumnLayout>
</div>

Accessibility​

What you get without doing anything:

  • ColumnLayout is plain <div>s with no role, because columns have no meaning of their own. Screen readers go straight to the children.
  • The reading order is always the order of the children, at every width. ColumnLayout never moves children around. In 1:2 the narrow child is read first, because it comes first.

What's up to you:

  • Order. Put the children in the order people should read them, even when one is the focus.

Styling​

WhatToken
Gap between columns and rows--shk-space-4 (16px)
Narrowest column (equal)--shk-size-24 × 2 (192px)

The 592px width where 2:1 and 1:2 switch to two columns is fixed: CSS doesn't allow it to be a token.