Card

A surface that groups content with shared spacing and corners. Choose outlined, subtle, elevated or placeholder; placeholder marks an empty content area with secondary text and a visible dashed border. Standard or expressive treatment independently follows the theme's material. Tones change colour without changing geometry. All slots are optional; content uses the default slot. Omit the header to keep the title and description together in the body. Use ActionBar in the actions slot, with the main action in its primary slot. CardGroup aligns these regions with native subgrid; ordinary cards keep their stacked layout.

Content card

A single surface with a heading and supporting text.

Team notes

A clear place for shared work.

Code

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

<ui-card
  padding="sm"
>
  <ui-stack
    gap="xs"
  >
    <ui-text
      variant="heading"
      as="h4"
    >Team notes</ui-text>
    <ui-text
      variant="caption"
    >A clear place for shared work.</ui-text>
  </ui-stack>
</ui-card>
<script setup>
  import { UiCard, UiStack, UiText } from "@threadlabs/looma";
</script>

<template>
  <UiCard
    padding="sm"
  >
    <UiStack
      gap="xs"
    >
      <UiText
        variant="heading"
        as="h4"
      >Team notes</UiText>
      <UiText
        variant="caption"
      >A clear place for shared work.</UiText>
    </UiStack>
  </UiCard>
</template>
import { UiCard, UiStack, UiText } from "@threadlabs/looma";

export default function Example() {
  return <UiCard
    padding="sm"
  >
    <UiStack
      gap="xs"
    >
      <UiText
        variant="heading"
        as="h4"
      >Team notes</UiText>
      <UiText
        variant="caption"
      >A clear place for shared work.</UiText>
    </UiStack>
  </UiCard>;
}
/** @jsxImportSource preact */
import { UiCard, UiStack, UiText } from "@threadlabs/looma";

export default function Example() {
  return <UiCard
    padding="sm"
  >
    <UiStack
      gap="xs"
    >
      <UiText
        variant="heading"
        as="h4"
      >Team notes</UiText>
      <UiText
        variant="caption"
      >A clear place for shared work.</UiText>
    </UiStack>
  </UiCard>;
}
<script>
  import { UiCard, UiStack, UiText } from "@threadlabs/looma";
</script>

<UiCard
  padding="sm"
>
  <UiStack
    gap="xs"
  >
    <UiText
      variant="heading"
      as="h4"
    >Team notes</UiText>
    <UiText
      variant="caption"
    >A clear place for shared work.</UiText>
  </UiStack>
</UiCard>
import { Component } from "@angular/core";
import { UiCard, UiStack, UiText } from "@threadlabs/looma";

@Component({
  selector: "example-view",
  standalone: true,
  imports: [UiCard, UiStack, UiText],
  template: `
    <ng-template
      ui-card
      [padding]="'sm'"
    >
      <div
        ui-stack
        [gap]="'xs'"
      >
        <ng-template
          ui-text
          [variant]="'heading'"
          [as]="'h4'"
        >Team notes</ng-template>
        <ng-template
          ui-text
          [variant]="'caption'"
        >A clear place for shared work.</ng-template>
      </div>
    </ng-template>
  `,
})
export default class Example {}

Variants

outlined groups content on the page, subtle sets supporting details apart, elevated floats a view's one card, and placeholder marks an empty content area.

Outlined

A bordered panel on the page.

Subtle: supporting details, set apart.

Elevated

The one card of a view, such as sign-in.

No content yet

This area is empty.

Code

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

<ui-stack
  gap="m"
>
  <ui-card>
    <ui-text
      weight="semibold"
    >Outlined</ui-text>
    <ui-text
      tone="secondary"
    >A bordered panel on the page.</ui-text>
  </ui-card>
  <ui-card
    variant="subtle"
    padding="sm"
  >
    <ui-text
      tone="secondary"
      size="sm"
    >Subtle: supporting details, set apart.</ui-text>
  </ui-card>
  <ui-card
    variant="elevated"
    padding="lg"
  >
    <ui-text
      weight="semibold"
    >Elevated</ui-text>
    <ui-text
      tone="secondary"
    >The one card of a view, such as sign-in.</ui-text>
  </ui-card>
  <ui-card
    variant="placeholder"
  >
    <ui-text
      weight="semibold"
    >No content yet</ui-text>
    <ui-text>This area is empty.</ui-text>
  </ui-card>
</ui-stack>
<script setup>
  import { UiCard, UiStack, UiText } from "@threadlabs/looma";
</script>

<template>
  <UiStack
    gap="m"
  >
    <UiCard>
      <UiText
        weight="semibold"
      >Outlined</UiText>
      <UiText
        tone="secondary"
      >A bordered panel on the page.</UiText>
    </UiCard>
    <UiCard
      variant="subtle"
      padding="sm"
    >
      <UiText
        tone="secondary"
        size="sm"
      >Subtle: supporting details, set apart.</UiText>
    </UiCard>
    <UiCard
      variant="elevated"
      padding="lg"
    >
      <UiText
        weight="semibold"
      >Elevated</UiText>
      <UiText
        tone="secondary"
      >The one card of a view, such as sign-in.</UiText>
    </UiCard>
    <UiCard
      variant="placeholder"
    >
      <UiText
        weight="semibold"
      >No content yet</UiText>
      <UiText>This area is empty.</UiText>
    </UiCard>
  </UiStack>
</template>
import { UiCard, UiStack, UiText } from "@threadlabs/looma";

export default function Example() {
  return <UiStack
    gap="m"
  >
    <UiCard>
      <UiText
        weight="semibold"
      >Outlined</UiText>
      <UiText
        tone="secondary"
      >A bordered panel on the page.</UiText>
    </UiCard>
    <UiCard
      variant="subtle"
      padding="sm"
    >
      <UiText
        tone="secondary"
        size="sm"
      >Subtle: supporting details, set apart.</UiText>
    </UiCard>
    <UiCard
      variant="elevated"
      padding="lg"
    >
      <UiText
        weight="semibold"
      >Elevated</UiText>
      <UiText
        tone="secondary"
      >The one card of a view, such as sign-in.</UiText>
    </UiCard>
    <UiCard
      variant="placeholder"
    >
      <UiText
        weight="semibold"
      >No content yet</UiText>
      <UiText>This area is empty.</UiText>
    </UiCard>
  </UiStack>;
}
/** @jsxImportSource preact */
import { UiCard, UiStack, UiText } from "@threadlabs/looma";

export default function Example() {
  return <UiStack
    gap="m"
  >
    <UiCard>
      <UiText
        weight="semibold"
      >Outlined</UiText>
      <UiText
        tone="secondary"
      >A bordered panel on the page.</UiText>
    </UiCard>
    <UiCard
      variant="subtle"
      padding="sm"
    >
      <UiText
        tone="secondary"
        size="sm"
      >Subtle: supporting details, set apart.</UiText>
    </UiCard>
    <UiCard
      variant="elevated"
      padding="lg"
    >
      <UiText
        weight="semibold"
      >Elevated</UiText>
      <UiText
        tone="secondary"
      >The one card of a view, such as sign-in.</UiText>
    </UiCard>
    <UiCard
      variant="placeholder"
    >
      <UiText
        weight="semibold"
      >No content yet</UiText>
      <UiText>This area is empty.</UiText>
    </UiCard>
  </UiStack>;
}
<script>
  import { UiCard, UiStack, UiText } from "@threadlabs/looma";
</script>

<UiStack
  gap="m"
>
  <UiCard>
    <UiText
      weight="semibold"
    >Outlined</UiText>
    <UiText
      tone="secondary"
    >A bordered panel on the page.</UiText>
  </UiCard>
  <UiCard
    variant="subtle"
    padding="sm"
  >
    <UiText
      tone="secondary"
      size="sm"
    >Subtle: supporting details, set apart.</UiText>
  </UiCard>
  <UiCard
    variant="elevated"
    padding="lg"
  >
    <UiText
      weight="semibold"
    >Elevated</UiText>
    <UiText
      tone="secondary"
    >The one card of a view, such as sign-in.</UiText>
  </UiCard>
  <UiCard
    variant="placeholder"
  >
    <UiText
      weight="semibold"
    >No content yet</UiText>
    <UiText>This area is empty.</UiText>
  </UiCard>
</UiStack>
import { Component } from "@angular/core";
import { UiCard, UiStack, UiText } from "@threadlabs/looma";

@Component({
  selector: "example-view",
  standalone: true,
  imports: [UiStack, UiCard, UiText],
  template: `
    <div
      ui-stack
      [gap]="'m'"
    >
      <ng-template
        ui-card
      >
        <ng-template
          ui-text
          [weight]="'semibold'"
        >Outlined</ng-template>
        <ng-template
          ui-text
          [tone]="'secondary'"
        >A bordered panel on the page.</ng-template>
      </ng-template>
      <ng-template
        ui-card
        [variant]="'subtle'"
        [padding]="'sm'"
      >
        <ng-template
          ui-text
          [tone]="'secondary'"
          [size]="'sm'"
        >Subtle: supporting details, set apart.</ng-template>
      </ng-template>
      <ng-template
        ui-card
        [variant]="'elevated'"
        [padding]="'lg'"
      >
        <ng-template
          ui-text
          [weight]="'semibold'"
        >Elevated</ng-template>
        <ng-template
          ui-text
          [tone]="'secondary'"
        >The one card of a view, such as sign-in.</ng-template>
      </ng-template>
      <ng-template
        ui-card
        [variant]="'placeholder'"
      >
        <ng-template
          ui-text
          [weight]="'semibold'"
        >No content yet</ng-template>
        <ng-template
          ui-text
        >This area is empty.</ng-template>
      </ng-template>
    </div>
  `,
})
export default class Example {}

Danger

tone="danger" tints a consequential section while retaining the same corners and border geometry as other cards.

Delete this site

Everything in it is deleted for everyone.

Code

<link
  rel="component"
  href="@threadlabs/looma/components/ui-card/ui-card.html"
>
<link
  rel="component"
  href="@threadlabs/looma/components/ui-text/ui-text.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-card
  tone="danger"
>
  <ui-text
    weight="semibold"
  >Delete this site</ui-text>
  <ui-text
    tone="secondary"
    size="sm"
  >Everything in it is deleted for everyone.</ui-text>
  <ui-cluster>
    <ui-button
      variant="solid"
      tone="danger"
    >Delete site</ui-button>
  </ui-cluster>
</ui-card>
<script setup>
  import { UiButton, UiCard, UiCluster, UiText } from "@threadlabs/looma";
</script>

<template>
  <UiCard
    tone="danger"
  >
    <UiText
      weight="semibold"
    >Delete this site</UiText>
    <UiText
      tone="secondary"
      size="sm"
    >Everything in it is deleted for everyone.</UiText>
    <UiCluster>
      <UiButton
        variant="solid"
        tone="danger"
      >Delete site</UiButton>
    </UiCluster>
  </UiCard>
</template>
import { UiButton, UiCard, UiCluster, UiText } from "@threadlabs/looma";

export default function Example() {
  return <UiCard
    tone="danger"
  >
    <UiText
      weight="semibold"
    >Delete this site</UiText>
    <UiText
      tone="secondary"
      size="sm"
    >Everything in it is deleted for everyone.</UiText>
    <UiCluster>
      <UiButton
        variant="solid"
        tone="danger"
      >Delete site</UiButton>
    </UiCluster>
  </UiCard>;
}
/** @jsxImportSource preact */
import { UiButton, UiCard, UiCluster, UiText } from "@threadlabs/looma";

export default function Example() {
  return <UiCard
    tone="danger"
  >
    <UiText
      weight="semibold"
    >Delete this site</UiText>
    <UiText
      tone="secondary"
      size="sm"
    >Everything in it is deleted for everyone.</UiText>
    <UiCluster>
      <UiButton
        variant="solid"
        tone="danger"
      >Delete site</UiButton>
    </UiCluster>
  </UiCard>;
}
<script>
  import { UiButton, UiCard, UiCluster, UiText } from "@threadlabs/looma";
</script>

<UiCard
  tone="danger"
>
  <UiText
    weight="semibold"
  >Delete this site</UiText>
  <UiText
    tone="secondary"
    size="sm"
  >Everything in it is deleted for everyone.</UiText>
  <UiCluster>
    <UiButton
      variant="solid"
      tone="danger"
    >Delete site</UiButton>
  </UiCluster>
</UiCard>
import { Component } from "@angular/core";
import { UiButton, UiCard, UiCluster, UiText } from "@threadlabs/looma";

@Component({
  selector: "example-view",
  standalone: true,
  imports: [UiCard, UiText, UiCluster, UiButton],
  template: `
    <ng-template
      ui-card
      [tone]="'danger'"
    >
      <ng-template
        ui-text
        [weight]="'semibold'"
      >Delete this site</ng-template>
      <ng-template
        ui-text
        [tone]="'secondary'"
        [size]="'sm'"
      >Everything in it is deleted for everyone.</ng-template>
      <div
        ui-cluster
      >
        <ng-template
          ui-button
          [variant]="'solid'"
          [tone]="'danger'"
        >Delete site</ng-template>
      </div>
    </ng-template>
  `,
})
export default class Example {}

Quiet semantic surfaces

Cards can group supporting content on a softly tinted surface. Their child controls retain the normal design-system treatment.

Suggestions to consider
Work in progress
Completed work
Useful context
Supporting feature
Related collection

Code

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

<ui-stack
  gap="l"
>
  <ui-card
    variant="subtle"
    tone="accent"
  >Suggestions to consider</ui-card>
  <ui-card
    variant="subtle"
    tone="warning"
  >Work in progress</ui-card>
  <ui-card
    variant="subtle"
    tone="success"
  >Completed work</ui-card>
  <ui-card
    variant="subtle"
    tone="info"
  >Useful context</ui-card>
  <ui-card
    variant="subtle"
    tone="accent-secondary"
  >Supporting feature</ui-card>
  <ui-card
    variant="subtle"
    tone="accent-tertiary"
  >Related collection</ui-card>
</ui-stack>
<script setup>
  import { UiCard, UiStack } from "@threadlabs/looma";
</script>

<template>
  <UiStack
    gap="l"
  >
    <UiCard
      variant="subtle"
      tone="accent"
    >Suggestions to consider</UiCard>
    <UiCard
      variant="subtle"
      tone="warning"
    >Work in progress</UiCard>
    <UiCard
      variant="subtle"
      tone="success"
    >Completed work</UiCard>
    <UiCard
      variant="subtle"
      tone="info"
    >Useful context</UiCard>
    <UiCard
      variant="subtle"
      tone="accent-secondary"
    >Supporting feature</UiCard>
    <UiCard
      variant="subtle"
      tone="accent-tertiary"
    >Related collection</UiCard>
  </UiStack>
</template>
import { UiCard, UiStack } from "@threadlabs/looma";

export default function Example() {
  return <UiStack
    gap="l"
  >
    <UiCard
      variant="subtle"
      tone="accent"
    >Suggestions to consider</UiCard>
    <UiCard
      variant="subtle"
      tone="warning"
    >Work in progress</UiCard>
    <UiCard
      variant="subtle"
      tone="success"
    >Completed work</UiCard>
    <UiCard
      variant="subtle"
      tone="info"
    >Useful context</UiCard>
    <UiCard
      variant="subtle"
      tone="accent-secondary"
    >Supporting feature</UiCard>
    <UiCard
      variant="subtle"
      tone="accent-tertiary"
    >Related collection</UiCard>
  </UiStack>;
}
/** @jsxImportSource preact */
import { UiCard, UiStack } from "@threadlabs/looma";

export default function Example() {
  return <UiStack
    gap="l"
  >
    <UiCard
      variant="subtle"
      tone="accent"
    >Suggestions to consider</UiCard>
    <UiCard
      variant="subtle"
      tone="warning"
    >Work in progress</UiCard>
    <UiCard
      variant="subtle"
      tone="success"
    >Completed work</UiCard>
    <UiCard
      variant="subtle"
      tone="info"
    >Useful context</UiCard>
    <UiCard
      variant="subtle"
      tone="accent-secondary"
    >Supporting feature</UiCard>
    <UiCard
      variant="subtle"
      tone="accent-tertiary"
    >Related collection</UiCard>
  </UiStack>;
}
<script>
  import { UiCard, UiStack } from "@threadlabs/looma";
</script>

<UiStack
  gap="l"
>
  <UiCard
    variant="subtle"
    tone="accent"
  >Suggestions to consider</UiCard>
  <UiCard
    variant="subtle"
    tone="warning"
  >Work in progress</UiCard>
  <UiCard
    variant="subtle"
    tone="success"
  >Completed work</UiCard>
  <UiCard
    variant="subtle"
    tone="info"
  >Useful context</UiCard>
  <UiCard
    variant="subtle"
    tone="accent-secondary"
  >Supporting feature</UiCard>
  <UiCard
    variant="subtle"
    tone="accent-tertiary"
  >Related collection</UiCard>
</UiStack>
import { Component } from "@angular/core";
import { UiCard, UiStack } from "@threadlabs/looma";

@Component({
  selector: "example-view",
  standalone: true,
  imports: [UiStack, UiCard],
  template: `
    <div
      ui-stack
      [gap]="'l'"
    >
      <ng-template
        ui-card
        [variant]="'subtle'"
        [tone]="'accent'"
      >Suggestions to consider</ng-template>
      <ng-template
        ui-card
        [variant]="'subtle'"
        [tone]="'warning'"
      >Work in progress</ng-template>
      <ng-template
        ui-card
        [variant]="'subtle'"
        [tone]="'success'"
      >Completed work</ng-template>
      <ng-template
        ui-card
        [variant]="'subtle'"
        [tone]="'info'"
      >Useful context</ng-template>
      <ng-template
        ui-card
        [variant]="'subtle'"
        [tone]="'accent-secondary'"
      >Supporting feature</ng-template>
      <ng-template
        ui-card
        [variant]="'subtle'"
        [tone]="'accent-tertiary'"
      >Related collection</ng-template>
    </div>
  `,
})
export default class Example {}

Expressive surface

Expressive treatment uses the theme's material independently of elevation. Bookish catches light through a frosted rim; a matte theme stays opaque. Ordinary elevated cards retain their subtle lift.

Fellowship office

Second breakfast briefing

Sam has prepared the agenda. Frodo is bringing the maps, and Sméagol has volunteered to take minutes.

Code

<link
  rel="component"
  href="@threadlabs/looma/components/ui-card/ui-card.html"
>
<link
  rel="component"
  href="@threadlabs/looma/components/ui-text/ui-text.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-card
  variant="elevated"
  treatment="expressive"
>
  <ui-text
    variant="eyebrow"
    tone="secondary"
  >Fellowship office</ui-text>
  <ui-text
    variant="heading"
    as="h2"
  >Second breakfast briefing</ui-text>
  <ui-text>Sam has prepared the agenda. Frodo is bringing the maps, and Sméagol has volunteered to take minutes.</ui-text>
  <ui-cluster>
    <ui-button
      preset="primary"
    >Join briefing</ui-button>
    <ui-button
      preset="quiet"
    >Read agenda</ui-button>
  </ui-cluster>
</ui-card>
<script setup>
  import { UiButton, UiCard, UiCluster, UiText } from "@threadlabs/looma";
</script>

<template>
  <UiCard
    variant="elevated"
    treatment="expressive"
  >
    <UiText
      variant="eyebrow"
      tone="secondary"
    >Fellowship office</UiText>
    <UiText
      variant="heading"
      as="h2"
    >Second breakfast briefing</UiText>
    <UiText>Sam has prepared the agenda. Frodo is bringing the maps, and Sméagol has volunteered to take minutes.</UiText>
    <UiCluster>
      <UiButton
        preset="primary"
      >Join briefing</UiButton>
      <UiButton
        preset="quiet"
      >Read agenda</UiButton>
    </UiCluster>
  </UiCard>
</template>
import { UiButton, UiCard, UiCluster, UiText } from "@threadlabs/looma";

export default function Example() {
  return <UiCard
    variant="elevated"
    treatment="expressive"
  >
    <UiText
      variant="eyebrow"
      tone="secondary"
    >Fellowship office</UiText>
    <UiText
      variant="heading"
      as="h2"
    >Second breakfast briefing</UiText>
    <UiText>Sam has prepared the agenda. Frodo is bringing the maps, and Sméagol has volunteered to take minutes.</UiText>
    <UiCluster>
      <UiButton
        preset="primary"
      >Join briefing</UiButton>
      <UiButton
        preset="quiet"
      >Read agenda</UiButton>
    </UiCluster>
  </UiCard>;
}
/** @jsxImportSource preact */
import { UiButton, UiCard, UiCluster, UiText } from "@threadlabs/looma";

export default function Example() {
  return <UiCard
    variant="elevated"
    treatment="expressive"
  >
    <UiText
      variant="eyebrow"
      tone="secondary"
    >Fellowship office</UiText>
    <UiText
      variant="heading"
      as="h2"
    >Second breakfast briefing</UiText>
    <UiText>Sam has prepared the agenda. Frodo is bringing the maps, and Sméagol has volunteered to take minutes.</UiText>
    <UiCluster>
      <UiButton
        preset="primary"
      >Join briefing</UiButton>
      <UiButton
        preset="quiet"
      >Read agenda</UiButton>
    </UiCluster>
  </UiCard>;
}
<script>
  import { UiButton, UiCard, UiCluster, UiText } from "@threadlabs/looma";
</script>

<UiCard
  variant="elevated"
  treatment="expressive"
>
  <UiText
    variant="eyebrow"
    tone="secondary"
  >Fellowship office</UiText>
  <UiText
    variant="heading"
    as="h2"
  >Second breakfast briefing</UiText>
  <UiText>Sam has prepared the agenda. Frodo is bringing the maps, and Sméagol has volunteered to take minutes.</UiText>
  <UiCluster>
    <UiButton
      preset="primary"
    >Join briefing</UiButton>
    <UiButton
      preset="quiet"
    >Read agenda</UiButton>
  </UiCluster>
</UiCard>
import { Component } from "@angular/core";
import { UiButton, UiCard, UiCluster, UiText } from "@threadlabs/looma";

@Component({
  selector: "example-view",
  standalone: true,
  imports: [UiCard, UiText, UiCluster, UiButton],
  template: `
    <ng-template
      ui-card
      [variant]="'elevated'"
      [treatment]="'expressive'"
    >
      <ng-template
        ui-text
        [variant]="'eyebrow'"
        [tone]="'secondary'"
      >Fellowship office</ng-template>
      <ng-template
        ui-text
        [variant]="'heading'"
        [as]="'h2'"
      >Second breakfast briefing</ng-template>
      <ng-template
        ui-text
      >Sam has prepared the agenda. Frodo is bringing the maps, and Sméagol has volunteered to take minutes.</ng-template>
      <div
        ui-cluster
      >
        <ng-template
          ui-button
          [preset]="'primary'"
        >Join briefing</ng-template>
        <ng-template
          ui-button
          [preset]="'quiet'"
        >Read agenda</ng-template>
      </div>
    </ng-template>
  `,
})
export default class Example {}

Fill a bounded column

Place this stack in a bounded-height parent. The surface fills the remaining space while its body scrolls independently.

Reference panel

A short list still has a full-height surface.

Longer content scrolls inside this panel.

Persistent controls stay below the panel.

Code

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

<ui-stack
  fill
  gap="s"
>
  <ui-text
    weight="semibold"
  >Reference panel</ui-text>
  <ui-card
    fill
    variant="subtle"
    tone="accent"
    padding="xs"
  >
    <ui-scroll-area
      fill
    >
      <ui-stack
        gap="s"
      >
        <ui-text>A short list still has a full-height surface.</ui-text>
        <ui-text>Longer content scrolls inside this panel.</ui-text>
      </ui-stack>
    </ui-scroll-area>
  </ui-card>
  <ui-text
    size="sm"
    tone="secondary"
  >Persistent controls stay below the panel.</ui-text>
</ui-stack>
<script setup>
  import { UiCard, UiScrollArea, UiStack, UiText } from "@threadlabs/looma";
</script>

<template>
  <UiStack
    :fill="true"
    gap="s"
  >
    <UiText
      weight="semibold"
    >Reference panel</UiText>
    <UiCard
      :fill="true"
      variant="subtle"
      tone="accent"
      padding="xs"
    >
      <UiScrollArea
        :fill="true"
      >
        <UiStack
          gap="s"
        >
          <UiText>A short list still has a full-height surface.</UiText>
          <UiText>Longer content scrolls inside this panel.</UiText>
        </UiStack>
      </UiScrollArea>
    </UiCard>
    <UiText
      size="sm"
      tone="secondary"
    >Persistent controls stay below the panel.</UiText>
  </UiStack>
</template>
import { UiCard, UiScrollArea, UiStack, UiText } from "@threadlabs/looma";

export default function Example() {
  return <UiStack
    fill
    gap="s"
  >
    <UiText
      weight="semibold"
    >Reference panel</UiText>
    <UiCard
      fill
      variant="subtle"
      tone="accent"
      padding="xs"
    >
      <UiScrollArea
        fill
      >
        <UiStack
          gap="s"
        >
          <UiText>A short list still has a full-height surface.</UiText>
          <UiText>Longer content scrolls inside this panel.</UiText>
        </UiStack>
      </UiScrollArea>
    </UiCard>
    <UiText
      size="sm"
      tone="secondary"
    >Persistent controls stay below the panel.</UiText>
  </UiStack>;
}
/** @jsxImportSource preact */
import { UiCard, UiScrollArea, UiStack, UiText } from "@threadlabs/looma";

export default function Example() {
  return <UiStack
    fill
    gap="s"
  >
    <UiText
      weight="semibold"
    >Reference panel</UiText>
    <UiCard
      fill
      variant="subtle"
      tone="accent"
      padding="xs"
    >
      <UiScrollArea
        fill
      >
        <UiStack
          gap="s"
        >
          <UiText>A short list still has a full-height surface.</UiText>
          <UiText>Longer content scrolls inside this panel.</UiText>
        </UiStack>
      </UiScrollArea>
    </UiCard>
    <UiText
      size="sm"
      tone="secondary"
    >Persistent controls stay below the panel.</UiText>
  </UiStack>;
}
<script>
  import { UiCard, UiScrollArea, UiStack, UiText } from "@threadlabs/looma";
</script>

<UiStack
  fill
  gap="s"
>
  <UiText
    weight="semibold"
  >Reference panel</UiText>
  <UiCard
    fill
    variant="subtle"
    tone="accent"
    padding="xs"
  >
    <UiScrollArea
      fill
    >
      <UiStack
        gap="s"
      >
        <UiText>A short list still has a full-height surface.</UiText>
        <UiText>Longer content scrolls inside this panel.</UiText>
      </UiStack>
    </UiScrollArea>
  </UiCard>
  <UiText
    size="sm"
    tone="secondary"
  >Persistent controls stay below the panel.</UiText>
</UiStack>
import { Component } from "@angular/core";
import { UiCard, UiScrollArea, UiStack, UiText } from "@threadlabs/looma";

@Component({
  selector: "example-view",
  standalone: true,
  imports: [UiStack, UiText, UiCard, UiScrollArea],
  template: `
    <div
      ui-stack
      [fill]="true"
      [gap]="'s'"
    >
      <ng-template
        ui-text
        [weight]="'semibold'"
      >Reference panel</ng-template>
      <ng-template
        ui-card
        [fill]="true"
        [variant]="'subtle'"
        [tone]="'accent'"
        [padding]="'xs'"
      >
        <div
          ui-scroll-area
          [fill]="true"
        >
          <div
            ui-stack
            [gap]="'s'"
          >
            <ng-template
              ui-text
            >A short list still has a full-height surface.</ng-template>
            <ng-template
              ui-text
            >Longer content scrolls inside this panel.</ng-template>
          </div>
        </div>
      </ng-template>
      <ng-template
        ui-text
        [size]="'sm'"
        [tone]="'secondary'"
      >Persistent controls stay below the panel.</ng-template>
    </div>
  `,
})
export default class Example {}

API

Package: @threadlabs/looma · Native root: div

Attributes

NameDescriptionPropertyTypeDefaultAllowed values
asThe native element the card renders. Choose the one that names the content: a section of a page, a self-contained article, or an aside.askeyworddivdiv, section, article, aside
fillIn a bounded flex column, grows into the remaining height. Put a filling Scroll Area in the body when its content needs to scroll independently.fillbooleanfalse—
paddingThe space inside the card: xs is compact (0.5rem), sm is snug (0.75rem), md is the default (1.5rem), and lg is roomy (2rem).paddingkeywordmdxs, sm, md, lg
sizeOptional size preset. content keeps the existing intrinsic sizing; the other sizes constrain the surface for compact, medium, or large content.sizekeywordcontentcontent, sm, md, lg
toneaccent, info, success, warning and danger add a quiet semantic surface without changing card geometry. Row highlights follow the surface hue; action controls retain their own intent. Supporting brand families use accent-secondary and accent-tertiary; their usable colors derive from the theme, and unset families follow the primary accent.tonekeywordneutralneutral, accent, accent-secondary, accent-tertiary, info, success, warning, danger
treatmentHow much of the theme's decorative material the surface expresses. standard preserves its restrained treatment; expressive uses the full material treatment. Its glass rim can transmit and frost the surrounding scene while content stays nearly opaque. Surface separation controls the rim, independently of frosting. This does not add elevation or require glass.treatmentkeywordstandardstandard, expressive
variantThe surface. outlined is a bordered panel on the page; subtle is a quiet filled one with no border, for supporting details set apart from the flow; elevated adds a subtle lift above the page, for a view's one card, such as sign-in. placeholder marks an empty content area with a visible dashed border and secondary text, keeping the outlined card's dimensions. Treatment independently controls decorative material.variantkeywordoutlinedoutlined, subtle, elevated, placeholder

Framework props

NameTypeDefaultAllowed values
as"div" | "section" | "article" | "aside"divdiv, section, article, aside
fillbooleanfalse—
padding"xs" | "sm" | "md" | "lg"mdxs, sm, md, lg
size"content" | "sm" | "md" | "lg"contentcontent, sm, md, lg
tone"neutral" | "accent" | "accent-secondary" | "accent-tertiary" | "info" | "success" | "warning" | "danger"neutralneutral, accent, accent-secondary, accent-tertiary, info, success, warning, danger
treatment"standard" | "expressive"standardstandard, expressive
variant"outlined" | "subtle" | "elevated" | "placeholder"outlinedoutlined, subtle, elevated, placeholder

Events

No events.

Slots

NamePurpose
headerNamed content for the header region.
defaultDefault child content.
actionsNamed content for the actions region.

Design tokens

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

Component tokens

No component tokens.

Shared tokens

TokenFallback
--ui-accent-secondary-soft—
--ui-accent-secondary-soft-border—
--ui-accent-secondary-subtle-text—
--ui-accent-soft—
--ui-accent-soft-border—
--ui-accent-subtle-text—
--ui-accent-tertiary-soft—
--ui-accent-tertiary-soft-border—
--ui-accent-tertiary-subtle-text—
--ui-border-strong—
--ui-border-subtle—
--ui-border-width—
--ui-danger-soft—
--ui-danger-soft-border—
--ui-danger-subtle-text—
--ui-glassiness0
--ui-info-soft—
--ui-info-soft-border—
--ui-info-subtle-text—
--ui-material-edgevar(--ui-border-strong)
--ui-material-glassvar(--ui-surface-raised)
--ui-material-glow0 0 transparent
--ui-material-highlightvar(--ui-surface-highlight, linear-gradient(transparent, transparent))
--ui-radius-lg—
--ui-shadow-lg—
--ui-space-1—
--ui-space-2—
--ui-space-3—
--ui-space-4—
--ui-space-5—
--ui-space-6—
--ui-success-soft—
--ui-success-soft-border—
--ui-success-subtle-text—
--ui-surface-highlightlinear-gradient(transparent, transparent)
--ui-surface-muted—
--ui-surface-raised—
--ui-surface-separation0
--ui-text-primary—
--ui-text-secondary—
--ui-warning-soft—
--ui-warning-soft-border—
--ui-warning-subtle-text—