Container

Container primitive for readable measure and horizontal gutters.

Default

With no properties supplied, Container caps its column at a 65ch measure, centers it in the available space, and pads it with default gutters.

The Fellowship office memo stays within a readable 65ch measure.

Code

<link
  rel="component"
  href="@threadlabs/looma/components/ui-container/ui-container.html"
>
<link
  rel="component"
  href="@threadlabs/looma/components/ui-card/ui-card.html"
>

<ui-container>
  <ui-card
    padding="sm"
  >The Fellowship office memo stays within a readable 65ch measure.</ui-card>
</ui-container>
<script setup>
  import { UiCard, UiContainer } from "@threadlabs/looma";
</script>

<template>
  <UiContainer>
    <UiCard
      padding="sm"
    >The Fellowship office memo stays within a readable 65ch measure.</UiCard>
  </UiContainer>
</template>
import { UiCard, UiContainer } from "@threadlabs/looma";

export default function Example() {
  return <UiContainer>
    <UiCard
      padding="sm"
    >The Fellowship office memo stays within a readable 65ch measure.</UiCard>
  </UiContainer>;
}
/** @jsxImportSource preact */
import { UiCard, UiContainer } from "@threadlabs/looma";

export default function Example() {
  return <UiContainer>
    <UiCard
      padding="sm"
    >The Fellowship office memo stays within a readable 65ch measure.</UiCard>
  </UiContainer>;
}
<script>
  import { UiCard, UiContainer } from "@threadlabs/looma";
</script>

<UiContainer>
  <UiCard
    padding="sm"
  >The Fellowship office memo stays within a readable 65ch measure.</UiCard>
</UiContainer>
import { Component } from "@angular/core";
import { UiCard, UiContainer } from "@threadlabs/looma";

@Component({
  selector: "example-view",
  standalone: true,
  imports: [UiContainer, UiCard],
  template: `
    <div
      ui-container
    >
      <ng-template
        ui-card
        [padding]="'sm'"
      >The Fellowship office memo stays within a readable 65ch measure.</ng-template>
    </div>
  `,
})
export default class Example {}

measure="narrow"

The narrow measure caps the column at 45ch; the tinted bands either side are the default gutters.

Narrow 45ch measure

Code

<link
  rel="component"
  href="@threadlabs/looma/components/ui-container/ui-container.html"
>

<ui-container
  measure="narrow"
>
  <div>Narrow 45ch measure</div>
</ui-container>
<script setup>
  import { UiContainer } from "@threadlabs/looma";
</script>

<template>
  <UiContainer
    measure="narrow"
  >
    <div>Narrow 45ch measure</div>
  </UiContainer>
</template>
import { UiContainer } from "@threadlabs/looma";

export default function Example() {
  return <UiContainer
    measure="narrow"
  >
    <div>Narrow 45ch measure</div>
  </UiContainer>;
}
/** @jsxImportSource preact */
import { UiContainer } from "@threadlabs/looma";

export default function Example() {
  return <UiContainer
    measure="narrow"
  >
    <div>Narrow 45ch measure</div>
  </UiContainer>;
}
<script>
  import { UiContainer } from "@threadlabs/looma";
</script>

<UiContainer
  measure="narrow"
>
  <div>Narrow 45ch measure</div>
</UiContainer>
import { Component } from "@angular/core";
import { UiContainer } from "@threadlabs/looma";

@Component({
  selector: "example-view",
  standalone: true,
  imports: [UiContainer],
  template: `
    <div
      ui-container
      [measure]="'narrow'"
    >
      <div>Narrow 45ch measure</div>
    </div>
  `,
})
export default class Example {}

gutters="l"

Larger gutters keep content away from the column edges, which matters most when the container is narrower than the measure.

Large gutters

Code

<link
  rel="component"
  href="@threadlabs/looma/components/ui-container/ui-container.html"
>

<div
  style="max-width: 20rem; margin-inline: auto"
>
  <ui-container
    gutters="l"
  >
    <div>Large gutters</div>
  </ui-container>
</div>
<script setup>
  import { UiContainer } from "@threadlabs/looma";
</script>

<template>
  <div
    style="max-width: 20rem; margin-inline: auto"
  >
    <UiContainer
      gutters="l"
    >
      <div>Large gutters</div>
    </UiContainer>
  </div>
</template>
import { UiContainer } from "@threadlabs/looma";

export default function Example() {
  return <div
    style={{"maxWidth":"20rem","marginInline":"auto"}}
  >
    <UiContainer
      gutters="l"
    >
      <div>Large gutters</div>
    </UiContainer>
  </div>;
}
/** @jsxImportSource preact */
import { UiContainer } from "@threadlabs/looma";

export default function Example() {
  return <div
    style={{"maxWidth":"20rem","marginInline":"auto"}}
  >
    <UiContainer
      gutters="l"
    >
      <div>Large gutters</div>
    </UiContainer>
  </div>;
}
<script>
  import { UiContainer } from "@threadlabs/looma";
</script>

<div
  style="max-width: 20rem; margin-inline: auto"
>
  <UiContainer
    gutters="l"
  >
    <div>Large gutters</div>
  </UiContainer>
</div>
import { Component } from "@angular/core";
import { UiContainer } from "@threadlabs/looma";

@Component({
  selector: "example-view",
  standalone: true,
  imports: [UiContainer],
  template: `
    <div
      style="max-width: 20rem; margin-inline: auto"
    >
      <div
        ui-container
        [gutters]="'l'"
      >
        <div>Large gutters</div>
      </div>
    </div>
  `,
})
export default class Example {}

Bounded reading panel

Fill keeps the readable measure while a nested Scroll Area takes the remaining height. The panel's heading and action stay visible.

Reading notes

A bounded reading panel keeps each line at a comfortable measure.

Longer notes scroll within that panel while the surrounding controls stay in place.

The same container can hold a short note or a longer collection without changing its outer geometry.

Use the panel's available height instead of fixing the height of every paragraph.

Code

<link
  rel="component"
  href="@threadlabs/looma/components/ui-stack/ui-stack.html"
>
<link
  rel="component"
  href="@threadlabs/looma/components/ui-container/ui-container.html"
>
<link
  rel="component"
  href="@threadlabs/looma/components/ui-text/ui-text.html"
>
<link
  rel="component"
  href="@threadlabs/looma/components/ui-scroll-area/ui-scroll-area.html"
>
<link
  rel="component"
  href="@threadlabs/looma/components/ui-cluster/ui-cluster.html"
>
<link
  rel="component"
  href="@threadlabs/looma/components/ui-button/ui-button.html"
>

<ui-stack
  height="240"
>
  <ui-container
    fill
  >
    <ui-stack
      gap="m"
      fill
    >
      <ui-text
        as="h3"
        weight="semibold"
      >Reading notes</ui-text>
      <ui-scroll-area
        fill
        tabindex="0"
        role="region"
        aria-label="Reading notes"
      >
        <ui-stack
          gap="m"
        >
          <ui-text>A bounded reading panel keeps each line at a comfortable measure.</ui-text>
          <ui-text>Longer notes scroll within that panel while the surrounding controls stay in place.</ui-text>
          <ui-text>The same container can hold a short note or a longer collection without changing its outer geometry.</ui-text>
          <ui-text>Use the panel's available height instead of fixing the height of every paragraph.</ui-text>
        </ui-stack>
      </ui-scroll-area>
      <ui-cluster
        justify="end"
      >
        <ui-button
          variant="outline"
        >Continue</ui-button>
      </ui-cluster>
    </ui-stack>
  </ui-container>
</ui-stack>
<script setup>
  import { UiButton, UiCluster, UiContainer, UiScrollArea, UiStack, UiText } from "@threadlabs/looma";
</script>

<template>
  <UiStack
    :height="240"
  >
    <UiContainer
      :fill="true"
    >
      <UiStack
        gap="m"
        :fill="true"
      >
        <UiText
          as="h3"
          weight="semibold"
        >Reading notes</UiText>
        <UiScrollArea
          :fill="true"
          tabindex="0"
          role="region"
          aria-label="Reading notes"
        >
          <UiStack
            gap="m"
          >
            <UiText>A bounded reading panel keeps each line at a comfortable measure.</UiText>
            <UiText>Longer notes scroll within that panel while the surrounding controls stay in place.</UiText>
            <UiText>The same container can hold a short note or a longer collection without changing its outer geometry.</UiText>
            <UiText>Use the panel's available height instead of fixing the height of every paragraph.</UiText>
          </UiStack>
        </UiScrollArea>
        <UiCluster
          justify="end"
        >
          <UiButton
            variant="outline"
          >Continue</UiButton>
        </UiCluster>
      </UiStack>
    </UiContainer>
  </UiStack>
</template>
import { UiButton, UiCluster, UiContainer, UiScrollArea, UiStack, UiText } from "@threadlabs/looma";

export default function Example() {
  return <UiStack
    height={240}
  >
    <UiContainer
      fill
    >
      <UiStack
        gap="m"
        fill
      >
        <UiText
          as="h3"
          weight="semibold"
        >Reading notes</UiText>
        <UiScrollArea
          fill
          tabIndex="0"
          role="region"
          aria-label="Reading notes"
        >
          <UiStack
            gap="m"
          >
            <UiText>A bounded reading panel keeps each line at a comfortable measure.</UiText>
            <UiText>Longer notes scroll within that panel while the surrounding controls stay in place.</UiText>
            <UiText>The same container can hold a short note or a longer collection without changing its outer geometry.</UiText>
            <UiText>Use the panel's available height instead of fixing the height of every paragraph.</UiText>
          </UiStack>
        </UiScrollArea>
        <UiCluster
          justify="end"
        >
          <UiButton
            variant="outline"
          >Continue</UiButton>
        </UiCluster>
      </UiStack>
    </UiContainer>
  </UiStack>;
}
/** @jsxImportSource preact */
import { UiButton, UiCluster, UiContainer, UiScrollArea, UiStack, UiText } from "@threadlabs/looma";

export default function Example() {
  return <UiStack
    height={240}
  >
    <UiContainer
      fill
    >
      <UiStack
        gap="m"
        fill
      >
        <UiText
          as="h3"
          weight="semibold"
        >Reading notes</UiText>
        <UiScrollArea
          fill
          tabIndex="0"
          role="region"
          aria-label="Reading notes"
        >
          <UiStack
            gap="m"
          >
            <UiText>A bounded reading panel keeps each line at a comfortable measure.</UiText>
            <UiText>Longer notes scroll within that panel while the surrounding controls stay in place.</UiText>
            <UiText>The same container can hold a short note or a longer collection without changing its outer geometry.</UiText>
            <UiText>Use the panel's available height instead of fixing the height of every paragraph.</UiText>
          </UiStack>
        </UiScrollArea>
        <UiCluster
          justify="end"
        >
          <UiButton
            variant="outline"
          >Continue</UiButton>
        </UiCluster>
      </UiStack>
    </UiContainer>
  </UiStack>;
}
<script>
  import { UiButton, UiCluster, UiContainer, UiScrollArea, UiStack, UiText } from "@threadlabs/looma";
</script>

<UiStack
  height={240}
>
  <UiContainer
    fill
  >
    <UiStack
      gap="m"
      fill
    >
      <UiText
        as="h3"
        weight="semibold"
      >Reading notes</UiText>
      <UiScrollArea
        fill
        tabindex="0"
        role="region"
        aria-label="Reading notes"
      >
        <UiStack
          gap="m"
        >
          <UiText>A bounded reading panel keeps each line at a comfortable measure.</UiText>
          <UiText>Longer notes scroll within that panel while the surrounding controls stay in place.</UiText>
          <UiText>The same container can hold a short note or a longer collection without changing its outer geometry.</UiText>
          <UiText>Use the panel's available height instead of fixing the height of every paragraph.</UiText>
        </UiStack>
      </UiScrollArea>
      <UiCluster
        justify="end"
      >
        <UiButton
          variant="outline"
        >Continue</UiButton>
      </UiCluster>
    </UiStack>
  </UiContainer>
</UiStack>
import { Component } from "@angular/core";
import { UiButton, UiCluster, UiContainer, UiScrollArea, UiStack, UiText } from "@threadlabs/looma";

@Component({
  selector: "example-view",
  standalone: true,
  imports: [UiStack, UiContainer, UiText, UiScrollArea, UiCluster, UiButton],
  template: `
    <div
      ui-stack
      [height]="240"
    >
      <div
        ui-container
        [fill]="true"
      >
        <div
          ui-stack
          [gap]="'m'"
          [fill]="true"
        >
          <ng-template
            ui-text
            [as]="'h3'"
            [weight]="'semibold'"
          >Reading notes</ng-template>
          <div
            ui-scroll-area
            [fill]="true"
            tabindex="0"
            role="region"
            aria-label="Reading notes"
          >
            <div
              ui-stack
              [gap]="'m'"
            >
              <ng-template
                ui-text
              >A bounded reading panel keeps each line at a comfortable measure.</ng-template>
              <ng-template
                ui-text
              >Longer notes scroll within that panel while the surrounding controls stay in place.</ng-template>
              <ng-template
                ui-text
              >The same container can hold a short note or a longer collection without changing its outer geometry.</ng-template>
              <ng-template
                ui-text
              >Use the panel's available height instead of fixing the height of every paragraph.</ng-template>
            </div>
          </div>
          <div
            ui-cluster
            [justify]="'end'"
          >
            <ng-template
              ui-button
              [variant]="'outline'"
            >Continue</ng-template>
          </div>
        </div>
      </div>
    </div>
  `,
})
export default class Example {}

API

Package: @threadlabs/looma · Native root: div

Attributes

NameDescriptionPropertyTypeDefaultAllowed values
fillKeeps the readable width while filling available layout space. Uses a flex column so scrolling children can shrink inside a bounded flex or grid parent. Omit it to keep the usual block layout.fillbooleanfalse—
guttersInline padding on both sides, which keeps content off the edges of a narrow screen: s is 0.75rem, m is 1rem (also the default), and l is 1.5rem.gutterskeyword—s, m, l
measureThe container's maximum width, in characters of the current font: narrow is 45ch, wide is 80ch, and unset is 65ch, a comfortable reading line. The container centres itself when its parent is wider. The --ui-container-measure custom property, set on :root, an ancestor, or the container itself, overrides it.measurekeyword—narrow, wide

Framework props

NameTypeDefaultAllowed values
fillbooleanfalse—
gutters"s" | "m" | "l"—s, m, l
measure"narrow" | "wide"—narrow, wide

Events

No events.

Slots

NamePurpose
defaultDefault child content.

Design tokens

Set shared theme properties on a theme boundary. Component hooks apply only to their owning component.

Component tokens

TokenDeclared valuesFallback
--ui-container-guttersvar(--ui-space-4) | var(--ui-space-3) | var(--ui-space-5)—
--ui-container-measure65ch | 45ch | 80ch—

Shared tokens

TokenFallback
--ui-space-3—
--ui-space-4—
--ui-space-5—