Grid

Responsive auto-fit grid primitive, with explicit fixed columns for aligned matrices and optional neutral or accent shading for chosen columns using the theme palette.

Default

With no properties supplied, Grid uses its default gap and minimum column size.

Frodo
Sam
Sméagol

Code

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

<ui-grid>
  <ui-card
    padding="sm"
  >Frodo</ui-card>
  <ui-card
    padding="sm"
  >Sam</ui-card>
  <ui-card
    padding="sm"
  >Sméagol</ui-card>
</ui-grid>
<script setup>
  import { UiCard, UiGrid } from "@threadlabs/looma";
</script>

<template>
  <UiGrid>
    <UiCard
      padding="sm"
    >Frodo</UiCard>
    <UiCard
      padding="sm"
    >Sam</UiCard>
    <UiCard
      padding="sm"
    >Sméagol</UiCard>
  </UiGrid>
</template>
import { UiCard, UiGrid } from "@threadlabs/looma";

export default function Example() {
  return <UiGrid>
    <UiCard
      padding="sm"
    >Frodo</UiCard>
    <UiCard
      padding="sm"
    >Sam</UiCard>
    <UiCard
      padding="sm"
    >Sméagol</UiCard>
  </UiGrid>;
}
/** @jsxImportSource preact */
import { UiCard, UiGrid } from "@threadlabs/looma";

export default function Example() {
  return <UiGrid>
    <UiCard
      padding="sm"
    >Frodo</UiCard>
    <UiCard
      padding="sm"
    >Sam</UiCard>
    <UiCard
      padding="sm"
    >Sméagol</UiCard>
  </UiGrid>;
}
<script>
  import { UiCard, UiGrid } from "@threadlabs/looma";
</script>

<UiGrid>
  <UiCard
    padding="sm"
  >Frodo</UiCard>
  <UiCard
    padding="sm"
  >Sam</UiCard>
  <UiCard
    padding="sm"
  >Sméagol</UiCard>
</UiGrid>
import { Component } from "@angular/core";
import { UiCard, UiGrid } from "@threadlabs/looma";

@Component({
  selector: "example-view",
  standalone: true,
  imports: [UiGrid, UiCard],
  template: `
    <div
      ui-grid
    >
      <ng-template
        ui-card
        [padding]="'sm'"
      >Frodo</ng-template>
      <ng-template
        ui-card
        [padding]="'sm'"
      >Sam</ng-template>
      <ng-template
        ui-card
        [padding]="'sm'"
      >Sméagol</ng-template>
    </div>
  `,
})
export default class Example {}

Gap and minimum column width

Gap controls spacing while min controls the intrinsic wrap point for columns.

Frodo
Sam
Sméagol
Merry

Code

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

<ui-grid
  gap="xs"
  min="sm"
>
  <ui-card
    padding="sm"
  >Frodo</ui-card>
  <ui-card
    padding="sm"
  >Sam</ui-card>
  <ui-card
    padding="sm"
  >Sméagol</ui-card>
  <ui-card
    padding="sm"
  >Merry</ui-card>
</ui-grid>
<script setup>
  import { UiCard, UiGrid } from "@threadlabs/looma";
</script>

<template>
  <UiGrid
    gap="xs"
    min="sm"
  >
    <UiCard
      padding="sm"
    >Frodo</UiCard>
    <UiCard
      padding="sm"
    >Sam</UiCard>
    <UiCard
      padding="sm"
    >Sméagol</UiCard>
    <UiCard
      padding="sm"
    >Merry</UiCard>
  </UiGrid>
</template>
import { UiCard, UiGrid } from "@threadlabs/looma";

export default function Example() {
  return <UiGrid
    gap="xs"
    min="sm"
  >
    <UiCard
      padding="sm"
    >Frodo</UiCard>
    <UiCard
      padding="sm"
    >Sam</UiCard>
    <UiCard
      padding="sm"
    >Sméagol</UiCard>
    <UiCard
      padding="sm"
    >Merry</UiCard>
  </UiGrid>;
}
/** @jsxImportSource preact */
import { UiCard, UiGrid } from "@threadlabs/looma";

export default function Example() {
  return <UiGrid
    gap="xs"
    min="sm"
  >
    <UiCard
      padding="sm"
    >Frodo</UiCard>
    <UiCard
      padding="sm"
    >Sam</UiCard>
    <UiCard
      padding="sm"
    >Sméagol</UiCard>
    <UiCard
      padding="sm"
    >Merry</UiCard>
  </UiGrid>;
}
<script>
  import { UiCard, UiGrid } from "@threadlabs/looma";
</script>

<UiGrid
  gap="xs"
  min="sm"
>
  <UiCard
    padding="sm"
  >Frodo</UiCard>
  <UiCard
    padding="sm"
  >Sam</UiCard>
  <UiCard
    padding="sm"
  >Sméagol</UiCard>
  <UiCard
    padding="sm"
  >Merry</UiCard>
</UiGrid>
import { Component } from "@angular/core";
import { UiCard, UiGrid } from "@threadlabs/looma";

@Component({
  selector: "example-view",
  standalone: true,
  imports: [UiGrid, UiCard],
  template: `
    <div
      ui-grid
      [gap]="'xs'"
      [min]="'sm'"
    >
      <ng-template
        ui-card
        [padding]="'sm'"
      >Frodo</ng-template>
      <ng-template
        ui-card
        [padding]="'sm'"
      >Sam</ng-template>
      <ng-template
        ui-card
        [padding]="'sm'"
      >Sméagol</ng-template>
      <ng-template
        ui-card
        [padding]="'sm'"
      >Merry</ng-template>
    </div>
  `,
})
export default class Example {}

Cap the column count

At most three cards sit in a row; the grid still drops to two or one as the container narrows.

One
Two
Three
Four
Five

Code

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

<ui-grid
  columns="3"
  min="sm"
>
  <ui-card>One</ui-card>
  <ui-card>Two</ui-card>
  <ui-card>Three</ui-card>
  <ui-card>Four</ui-card>
  <ui-card>Five</ui-card>
</ui-grid>
<script setup>
  import { UiCard, UiGrid } from "@threadlabs/looma";
</script>

<template>
  <UiGrid
    :columns="3"
    min="sm"
  >
    <UiCard>One</UiCard>
    <UiCard>Two</UiCard>
    <UiCard>Three</UiCard>
    <UiCard>Four</UiCard>
    <UiCard>Five</UiCard>
  </UiGrid>
</template>
import { UiCard, UiGrid } from "@threadlabs/looma";

export default function Example() {
  return <UiGrid
    columns={3}
    min="sm"
  >
    <UiCard>One</UiCard>
    <UiCard>Two</UiCard>
    <UiCard>Three</UiCard>
    <UiCard>Four</UiCard>
    <UiCard>Five</UiCard>
  </UiGrid>;
}
/** @jsxImportSource preact */
import { UiCard, UiGrid } from "@threadlabs/looma";

export default function Example() {
  return <UiGrid
    columns={3}
    min="sm"
  >
    <UiCard>One</UiCard>
    <UiCard>Two</UiCard>
    <UiCard>Three</UiCard>
    <UiCard>Four</UiCard>
    <UiCard>Five</UiCard>
  </UiGrid>;
}
<script>
  import { UiCard, UiGrid } from "@threadlabs/looma";
</script>

<UiGrid
  columns={3}
  min="sm"
>
  <UiCard>One</UiCard>
  <UiCard>Two</UiCard>
  <UiCard>Three</UiCard>
  <UiCard>Four</UiCard>
  <UiCard>Five</UiCard>
</UiGrid>
import { Component } from "@angular/core";
import { UiCard, UiGrid } from "@threadlabs/looma";

@Component({
  selector: "example-view",
  standalone: true,
  imports: [UiGrid, UiCard],
  template: `
    <div
      ui-grid
      [columns]="3"
      [min]="'sm'"
    >
      <ng-template
        ui-card
      >One</ng-template>
      <ng-template
        ui-card
      >Two</ng-template>
      <ng-template
        ui-card
      >Three</ng-template>
      <ng-template
        ui-card
      >Four</ng-template>
      <ng-template
        ui-card
      >Five</ng-template>
    </div>
  `,
})
export default class Example {}
Explore the example at a narrower width.

Fixed columns

A matrix keeps its seven weekday columns at narrow widths. Ordinary grids retain their responsive column behavior.

Mon

Tue

Wed

Thu

Fri

Sat

Sun

Code

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

<ui-grid
  columns="7"
  fixed
  gap="none"
>
  <ui-text
    align="center"
  >Mon</ui-text>
  <ui-text
    align="center"
  >Tue</ui-text>
  <ui-text
    align="center"
  >Wed</ui-text>
  <ui-text
    align="center"
  >Thu</ui-text>
  <ui-text
    align="center"
  >Fri</ui-text>
  <ui-text
    align="center"
  >Sat</ui-text>
  <ui-text
    align="center"
  >Sun</ui-text>
</ui-grid>
<script setup>
  import { UiGrid, UiText } from "@threadlabs/looma";
</script>

<template>
  <UiGrid
    :columns="7"
    :fixed="true"
    gap="none"
  >
    <UiText
      align="center"
    >Mon</UiText>
    <UiText
      align="center"
    >Tue</UiText>
    <UiText
      align="center"
    >Wed</UiText>
    <UiText
      align="center"
    >Thu</UiText>
    <UiText
      align="center"
    >Fri</UiText>
    <UiText
      align="center"
    >Sat</UiText>
    <UiText
      align="center"
    >Sun</UiText>
  </UiGrid>
</template>
import { UiGrid, UiText } from "@threadlabs/looma";

export default function Example() {
  return <UiGrid
    columns={7}
    fixed
    gap="none"
  >
    <UiText
      align="center"
    >Mon</UiText>
    <UiText
      align="center"
    >Tue</UiText>
    <UiText
      align="center"
    >Wed</UiText>
    <UiText
      align="center"
    >Thu</UiText>
    <UiText
      align="center"
    >Fri</UiText>
    <UiText
      align="center"
    >Sat</UiText>
    <UiText
      align="center"
    >Sun</UiText>
  </UiGrid>;
}
/** @jsxImportSource preact */
import { UiGrid, UiText } from "@threadlabs/looma";

export default function Example() {
  return <UiGrid
    columns={7}
    fixed
    gap="none"
  >
    <UiText
      align="center"
    >Mon</UiText>
    <UiText
      align="center"
    >Tue</UiText>
    <UiText
      align="center"
    >Wed</UiText>
    <UiText
      align="center"
    >Thu</UiText>
    <UiText
      align="center"
    >Fri</UiText>
    <UiText
      align="center"
    >Sat</UiText>
    <UiText
      align="center"
    >Sun</UiText>
  </UiGrid>;
}
<script>
  import { UiGrid, UiText } from "@threadlabs/looma";
</script>

<UiGrid
  columns={7}
  fixed
  gap="none"
>
  <UiText
    align="center"
  >Mon</UiText>
  <UiText
    align="center"
  >Tue</UiText>
  <UiText
    align="center"
  >Wed</UiText>
  <UiText
    align="center"
  >Thu</UiText>
  <UiText
    align="center"
  >Fri</UiText>
  <UiText
    align="center"
  >Sat</UiText>
  <UiText
    align="center"
  >Sun</UiText>
</UiGrid>
import { Component } from "@angular/core";
import { UiGrid, UiText } from "@threadlabs/looma";

@Component({
  selector: "example-view",
  standalone: true,
  imports: [UiGrid, UiText],
  template: `
    <div
      ui-grid
      [columns]="7"
      [fixed]="true"
      [gap]="'none'"
    >
      <ng-template
        ui-text
        [align]="'center'"
      >Mon</ng-template>
      <ng-template
        ui-text
        [align]="'center'"
      >Tue</ng-template>
      <ng-template
        ui-text
        [align]="'center'"
      >Wed</ng-template>
      <ng-template
        ui-text
        [align]="'center'"
      >Thu</ng-template>
      <ng-template
        ui-text
        [align]="'center'"
      >Fri</ng-template>
      <ng-template
        ui-text
        [align]="'center'"
      >Sat</ng-template>
      <ng-template
        ui-text
        [align]="'center'"
      >Sun</ng-template>
    </div>
  `,
})
export default class Example {}

Shaded columns

Neutral washes identify selected matrix columns without changing their content, layout or writing direction. shade-tone="accent" gives header rows an accent tint. Column indices are one-based and use explicit fixed columns.

Sun

Mon

Tue

Wed

Thu

Fri

Sat

Code

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

<ui-grid
  fixed
  columns="7"
  gap="none"
  shaded-columns="[1,7]"
>
  <ui-text
    align="center"
  >Sun</ui-text>
  <ui-text
    align="center"
  >Mon</ui-text>
  <ui-text
    align="center"
  >Tue</ui-text>
  <ui-text
    align="center"
  >Wed</ui-text>
  <ui-text
    align="center"
  >Thu</ui-text>
  <ui-text
    align="center"
  >Fri</ui-text>
  <ui-text
    align="center"
  >Sat</ui-text>
</ui-grid>
<script setup>
  import { UiGrid, UiText } from "@threadlabs/looma";
</script>

<template>
  <UiGrid
    :fixed="true"
    :columns="7"
    gap="none"
    :shaded-columns="[1, 7]"
  >
    <UiText
      align="center"
    >Sun</UiText>
    <UiText
      align="center"
    >Mon</UiText>
    <UiText
      align="center"
    >Tue</UiText>
    <UiText
      align="center"
    >Wed</UiText>
    <UiText
      align="center"
    >Thu</UiText>
    <UiText
      align="center"
    >Fri</UiText>
    <UiText
      align="center"
    >Sat</UiText>
  </UiGrid>
</template>
import { UiGrid, UiText } from "@threadlabs/looma";

export default function Example() {
  return <UiGrid
    fixed
    columns={7}
    gap="none"
    shadedColumns={[1,7]}
  >
    <UiText
      align="center"
    >Sun</UiText>
    <UiText
      align="center"
    >Mon</UiText>
    <UiText
      align="center"
    >Tue</UiText>
    <UiText
      align="center"
    >Wed</UiText>
    <UiText
      align="center"
    >Thu</UiText>
    <UiText
      align="center"
    >Fri</UiText>
    <UiText
      align="center"
    >Sat</UiText>
  </UiGrid>;
}
/** @jsxImportSource preact */
import { UiGrid, UiText } from "@threadlabs/looma";

export default function Example() {
  return <UiGrid
    fixed
    columns={7}
    gap="none"
    shadedColumns={[1,7]}
  >
    <UiText
      align="center"
    >Sun</UiText>
    <UiText
      align="center"
    >Mon</UiText>
    <UiText
      align="center"
    >Tue</UiText>
    <UiText
      align="center"
    >Wed</UiText>
    <UiText
      align="center"
    >Thu</UiText>
    <UiText
      align="center"
    >Fri</UiText>
    <UiText
      align="center"
    >Sat</UiText>
  </UiGrid>;
}
<script>
  import { UiGrid, UiText } from "@threadlabs/looma";
</script>

<UiGrid
  fixed
  columns={7}
  gap="none"
  shadedColumns={[1,7]}
>
  <UiText
    align="center"
  >Sun</UiText>
  <UiText
    align="center"
  >Mon</UiText>
  <UiText
    align="center"
  >Tue</UiText>
  <UiText
    align="center"
  >Wed</UiText>
  <UiText
    align="center"
  >Thu</UiText>
  <UiText
    align="center"
  >Fri</UiText>
  <UiText
    align="center"
  >Sat</UiText>
</UiGrid>
import { Component } from "@angular/core";
import { UiGrid, UiText } from "@threadlabs/looma";

@Component({
  selector: "example-view",
  standalone: true,
  imports: [UiGrid, UiText],
  template: `
    <div
      ui-grid
      [fixed]="true"
      [columns]="7"
      [gap]="'none'"
      [shadedColumns]="[1, 7]"
    >
      <ng-template
        ui-text
        [align]="'center'"
      >Sun</ng-template>
      <ng-template
        ui-text
        [align]="'center'"
      >Mon</ng-template>
      <ng-template
        ui-text
        [align]="'center'"
      >Tue</ng-template>
      <ng-template
        ui-text
        [align]="'center'"
      >Wed</ng-template>
      <ng-template
        ui-text
        [align]="'center'"
      >Thu</ng-template>
      <ng-template
        ui-text
        [align]="'center'"
      >Fri</ng-template>
      <ng-template
        ui-text
        [align]="'center'"
      >Sat</ng-template>
    </div>
  `,
})
export default class Example {}

API

Package: @threadlabs/looma · Native root: div

Attributes

NameDescriptionPropertyTypeDefaultAllowed values
columnsMaximum columns when there is room. Omit for no cap. Columns normally decrease as the container narrows; fixed opts into an exact count.columnsinteger——
fixedKeeps the declared columns at every width, for aligned matrices such as weekday grids. Cells share the available width; omitted columns gives one column.fixedbooleanfalse—
gapSpace between cells, on the spacing scale: none removes the gap, xs is 0.5rem, s 0.75rem, m 1rem (also the default), l 1.5rem, and xl 2rem. The --ui-grid-gap custom property, set on :root, an ancestor, or the component itself, overrides it.gapkeyword—none, xs, s, m, l, xl
minThe narrowest a column may get before the grid drops to fewer columns: sm is 12rem (also the default), md is 16rem, and lg is 20rem. The grid fits as many equal columns as its width allows, and a single column never overflows a narrower container.minkeyword—sm, md, lg
selection-endLast selected one-based column, inclusive. The interval combines the theme's selection surface and border colors; --ui-grid-selection-inset and --ui-grid-selection-surface customize its inset and paint.selectionEndinteger——
selection-startFirst selected one-based column in a fixed grid row. Pair with selection-end to paint a continuous rounded interval behind the cells.selectionStartinteger——
shade-toneThe shaded columns' paint: neutral uses the theme's ink; accent uses its readable accent pigment. Both are subtle washes over the current surface.shadeTonekeywordneutralneutral, accent
shaded-columnsOne-based columns to shade in a fixed grid with explicit columns, such as nonworking days in a date matrix. A soft wash of the theme's ink distinguishes them from the canvas; --ui-grid-shade-surface can replace it. Shading follows writing direction and does not change cell sizing.shadedColumnslist(integer)[]—

Framework props

NameTypeDefaultAllowed values
columnsnumber——
fixedbooleanfalse—
gap"none" | "xs" | "s" | "m" | "l" | "xl"—none, xs, s, m, l, xl
min"sm" | "md" | "lg"—sm, md, lg
selectionEndnumber——
selectionStartnumber——
shadedColumnsreadonly (number)[][]—
shadeTone"neutral" | "accent"neutralneutral, accent

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-grid-gapvar(--ui-space-4) | var(--ui-space-2) | 0px | var(--ui-space-3) | var(--ui-space-5) | var(--ui-space-6)—
--ui-grid-min12rem | 16rem | 20rem—
--ui-grid-selection-inset—var(--ui-space-1)
--ui-grid-selection-surface—light-dark( color-mix(in oklab, var(--ui-selection-border) 80%, var(--ui-selection-surface)), color-mix(in oklab, var(--ui-selection-border) 28%, var(--ui-selection-surface)) )
--ui-grid-shade-surfacecolor-mix(in oklab, var(--ui-text) 8%, transparent) | color-mix(in oklab, var(--ui-accent-subtle-text) 12%, transparent)—

Shared tokens

TokenFallback
--ui-accent-subtle-text—
--ui-radius-round—
--ui-selection-border—
--ui-selection-surface—
--ui-space-1—
--ui-space-2—
--ui-space-3—
--ui-space-4—
--ui-space-5—
--ui-space-6—
--ui-text—