Cluster
Cluster layout primitive for row-style content with wrapping support. Set wrap="nowrap" to keep a small group, such as a meter and its value, on one row. Set maxRows with an overflow slot to show complete rows and reveal the remaining items through your action. The optional trailing slot stays visible within the row limit. The overflow-change event reports the hidden count. Omit maxRows for ordinary unbounded wrapping.
Default
With no properties supplied, Cluster lays tags out in a row with the default gap and wraps them onto new lines when the row runs out of space.
Code
<link
rel="component"
href="@threadlabs/looma/components/ui-cluster/ui-cluster.html"
>
<link
rel="component"
href="@threadlabs/looma/components/ui-badge/ui-badge.html"
>
<div
style="max-width: 18rem"
>
<ui-cluster>
<ui-badge>Design</ui-badge>
<ui-badge>Accessibility</ui-badge>
<ui-badge>Performance</ui-badge>
<ui-badge>Docs</ui-badge>
<ui-badge>Testing</ui-badge>
<ui-badge>Release</ui-badge>
</ui-cluster>
</div><script setup>
import { UiBadge, UiCluster } from "@threadlabs/looma";
</script>
<template>
<div
style="max-width: 18rem"
>
<UiCluster>
<UiBadge>Design</UiBadge>
<UiBadge>Accessibility</UiBadge>
<UiBadge>Performance</UiBadge>
<UiBadge>Docs</UiBadge>
<UiBadge>Testing</UiBadge>
<UiBadge>Release</UiBadge>
</UiCluster>
</div>
</template>import { UiBadge, UiCluster } from "@threadlabs/looma";
export default function Example() {
return <div
style={{"maxWidth":"18rem"}}
>
<UiCluster>
<UiBadge>Design</UiBadge>
<UiBadge>Accessibility</UiBadge>
<UiBadge>Performance</UiBadge>
<UiBadge>Docs</UiBadge>
<UiBadge>Testing</UiBadge>
<UiBadge>Release</UiBadge>
</UiCluster>
</div>;
}/** @jsxImportSource preact */
import { UiBadge, UiCluster } from "@threadlabs/looma";
export default function Example() {
return <div
style={{"maxWidth":"18rem"}}
>
<UiCluster>
<UiBadge>Design</UiBadge>
<UiBadge>Accessibility</UiBadge>
<UiBadge>Performance</UiBadge>
<UiBadge>Docs</UiBadge>
<UiBadge>Testing</UiBadge>
<UiBadge>Release</UiBadge>
</UiCluster>
</div>;
}<script>
import { UiBadge, UiCluster } from "@threadlabs/looma";
</script>
<div
style="max-width: 18rem"
>
<UiCluster>
<UiBadge>Design</UiBadge>
<UiBadge>Accessibility</UiBadge>
<UiBadge>Performance</UiBadge>
<UiBadge>Docs</UiBadge>
<UiBadge>Testing</UiBadge>
<UiBadge>Release</UiBadge>
</UiCluster>
</div>import { Component } from "@angular/core";
import { UiBadge, UiCluster } from "@threadlabs/looma";
@Component({
selector: "example-view",
standalone: true,
imports: [UiCluster, UiBadge],
template: `
<div
style="max-width: 18rem"
>
<div
ui-cluster
>
<span
ui-badge
>Design</span>
<span
ui-badge
>Accessibility</span>
<span
ui-badge
>Performance</span>
<span
ui-badge
>Docs</span>
<span
ui-badge
>Testing</span>
<span
ui-badge
>Release</span>
</div>
</div>
`,
})
export default class Example {}gap="l"
The large gap token adds more space between tags, both along each row and between wrapped rows.
Code
<link
rel="component"
href="@threadlabs/looma/components/ui-cluster/ui-cluster.html"
>
<link
rel="component"
href="@threadlabs/looma/components/ui-badge/ui-badge.html"
>
<div
style="max-width: 18rem"
>
<ui-cluster
gap="l"
>
<ui-badge>Design</ui-badge>
<ui-badge>Accessibility</ui-badge>
<ui-badge>Performance</ui-badge>
<ui-badge>Docs</ui-badge>
<ui-badge>Testing</ui-badge>
<ui-badge>Release</ui-badge>
</ui-cluster>
</div><script setup>
import { UiBadge, UiCluster } from "@threadlabs/looma";
</script>
<template>
<div
style="max-width: 18rem"
>
<UiCluster
gap="l"
>
<UiBadge>Design</UiBadge>
<UiBadge>Accessibility</UiBadge>
<UiBadge>Performance</UiBadge>
<UiBadge>Docs</UiBadge>
<UiBadge>Testing</UiBadge>
<UiBadge>Release</UiBadge>
</UiCluster>
</div>
</template>import { UiBadge, UiCluster } from "@threadlabs/looma";
export default function Example() {
return <div
style={{"maxWidth":"18rem"}}
>
<UiCluster
gap="l"
>
<UiBadge>Design</UiBadge>
<UiBadge>Accessibility</UiBadge>
<UiBadge>Performance</UiBadge>
<UiBadge>Docs</UiBadge>
<UiBadge>Testing</UiBadge>
<UiBadge>Release</UiBadge>
</UiCluster>
</div>;
}/** @jsxImportSource preact */
import { UiBadge, UiCluster } from "@threadlabs/looma";
export default function Example() {
return <div
style={{"maxWidth":"18rem"}}
>
<UiCluster
gap="l"
>
<UiBadge>Design</UiBadge>
<UiBadge>Accessibility</UiBadge>
<UiBadge>Performance</UiBadge>
<UiBadge>Docs</UiBadge>
<UiBadge>Testing</UiBadge>
<UiBadge>Release</UiBadge>
</UiCluster>
</div>;
}<script>
import { UiBadge, UiCluster } from "@threadlabs/looma";
</script>
<div
style="max-width: 18rem"
>
<UiCluster
gap="l"
>
<UiBadge>Design</UiBadge>
<UiBadge>Accessibility</UiBadge>
<UiBadge>Performance</UiBadge>
<UiBadge>Docs</UiBadge>
<UiBadge>Testing</UiBadge>
<UiBadge>Release</UiBadge>
</UiCluster>
</div>import { Component } from "@angular/core";
import { UiBadge, UiCluster } from "@threadlabs/looma";
@Component({
selector: "example-view",
standalone: true,
imports: [UiCluster, UiBadge],
template: `
<div
style="max-width: 18rem"
>
<div
ui-cluster
[gap]="'l'"
>
<span
ui-badge
>Design</span>
<span
ui-badge
>Accessibility</span>
<span
ui-badge
>Performance</span>
<span
ui-badge
>Docs</span>
<span
ui-badge
>Testing</span>
<span
ui-badge
>Release</span>
</div>
</div>
`,
})
export default class Example {}align="end"
End alignment makes items of different heights share a bottom edge within each row.
Code
<link
rel="component"
href="@threadlabs/looma/components/ui-cluster/ui-cluster.html"
>
<link
rel="component"
href="@threadlabs/looma/components/ui-badge/ui-badge.html"
>
<link
rel="component"
href="@threadlabs/looma/components/ui-button/ui-button.html"
>
<ui-cluster
gap="s"
align="end"
>
<ui-badge>Design</ui-badge>
<ui-badge>Docs</ui-badge>
<ui-button
size="sm"
>Add tag</ui-button>
</ui-cluster><script setup>
import { UiBadge, UiButton, UiCluster } from "@threadlabs/looma";
</script>
<template>
<UiCluster
gap="s"
align="end"
>
<UiBadge>Design</UiBadge>
<UiBadge>Docs</UiBadge>
<UiButton
size="sm"
>Add tag</UiButton>
</UiCluster>
</template>import { UiBadge, UiButton, UiCluster } from "@threadlabs/looma";
export default function Example() {
return <UiCluster
gap="s"
align="end"
>
<UiBadge>Design</UiBadge>
<UiBadge>Docs</UiBadge>
<UiButton
size="sm"
>Add tag</UiButton>
</UiCluster>;
}/** @jsxImportSource preact */
import { UiBadge, UiButton, UiCluster } from "@threadlabs/looma";
export default function Example() {
return <UiCluster
gap="s"
align="end"
>
<UiBadge>Design</UiBadge>
<UiBadge>Docs</UiBadge>
<UiButton
size="sm"
>Add tag</UiButton>
</UiCluster>;
}<script>
import { UiBadge, UiButton, UiCluster } from "@threadlabs/looma";
</script>
<UiCluster
gap="s"
align="end"
>
<UiBadge>Design</UiBadge>
<UiBadge>Docs</UiBadge>
<UiButton
size="sm"
>Add tag</UiButton>
</UiCluster>import { Component } from "@angular/core";
import { UiBadge, UiButton, UiCluster } from "@threadlabs/looma";
@Component({
selector: "example-view",
standalone: true,
imports: [UiCluster, UiBadge, UiButton],
template: `
<div
ui-cluster
[gap]="'s'"
[align]="'end'"
>
<span
ui-badge
>Design</span>
<span
ui-badge
>Docs</span>
<ng-template
ui-button
[size]="'sm'"
>Add tag</ng-template>
</div>
`,
})
export default class Example {}justify="between"
Justify places items along each row; between pushes the first and last items to the ends, as for a heading with its actions.
Members
Code
<link
rel="component"
href="@threadlabs/looma/components/ui-cluster/ui-cluster.html"
>
<link
rel="component"
href="@threadlabs/looma/components/ui-text/ui-text.html"
>
<link
rel="component"
href="@threadlabs/looma/components/ui-button/ui-button.html"
>
<ui-cluster
justify="between"
>
<ui-text
weight="semibold"
>Members</ui-text>
<ui-button
size="sm"
>Invite</ui-button>
</ui-cluster><script setup>
import { UiButton, UiCluster, UiText } from "@threadlabs/looma";
</script>
<template>
<UiCluster
justify="between"
>
<UiText
weight="semibold"
>Members</UiText>
<UiButton
size="sm"
>Invite</UiButton>
</UiCluster>
</template>import { UiButton, UiCluster, UiText } from "@threadlabs/looma";
export default function Example() {
return <UiCluster
justify="between"
>
<UiText
weight="semibold"
>Members</UiText>
<UiButton
size="sm"
>Invite</UiButton>
</UiCluster>;
}/** @jsxImportSource preact */
import { UiButton, UiCluster, UiText } from "@threadlabs/looma";
export default function Example() {
return <UiCluster
justify="between"
>
<UiText
weight="semibold"
>Members</UiText>
<UiButton
size="sm"
>Invite</UiButton>
</UiCluster>;
}<script>
import { UiButton, UiCluster, UiText } from "@threadlabs/looma";
</script>
<UiCluster
justify="between"
>
<UiText
weight="semibold"
>Members</UiText>
<UiButton
size="sm"
>Invite</UiButton>
</UiCluster>import { Component } from "@angular/core";
import { UiButton, UiCluster, UiText } from "@threadlabs/looma";
@Component({
selector: "example-view",
standalone: true,
imports: [UiCluster, UiText, UiButton],
template: `
<div
ui-cluster
[justify]="'between'"
>
<ng-template
ui-text
[weight]="'semibold'"
>Members</ng-template>
<ng-template
ui-button
[size]="'sm'"
>Invite</ng-template>
</div>
`,
})
export default class Example {}wrap="nowrap"
Nowrap keeps a small group on one row, so a meter stays beside its amount even in a narrow space.
Code
<link
rel="component"
href="@threadlabs/looma/components/ui-cluster/ui-cluster.html"
>
<link
rel="component"
href="@threadlabs/looma/components/ui-meter/ui-meter.html"
>
<div
style="max-width: 6rem"
>
<ui-cluster
gap="s"
wrap="nowrap"
> <ui-meter
size="sm"
value="750"
max="1240"
tone="info"
label="Collected"
value-text="$750 of $1,240"
></ui-meter> $750.00 </ui-cluster>
</div><script setup>
import { UiCluster, UiMeter } from "@threadlabs/looma";
</script>
<template>
<div
style="max-width: 6rem"
>
<UiCluster
gap="s"
wrap="nowrap"
> <UiMeter
size="sm"
:value="750"
:max="1240"
tone="info"
label="Collected"
value-text="$750 of $1,240"
/> $750.00 </UiCluster>
</div>
</template>import { UiCluster, UiMeter } from "@threadlabs/looma";
export default function Example() {
return <div
style={{"maxWidth":"6rem"}}
>
<UiCluster
gap="s"
wrap="nowrap"
>{" "}<UiMeter
size="sm"
value={750}
max={1240}
tone="info"
label="Collected"
valueText="$750 of $1,240"
/>{" $750.00 "}</UiCluster>
</div>;
}/** @jsxImportSource preact */
import { UiCluster, UiMeter } from "@threadlabs/looma";
export default function Example() {
return <div
style={{"maxWidth":"6rem"}}
>
<UiCluster
gap="s"
wrap="nowrap"
>{" "}<UiMeter
size="sm"
value={750}
max={1240}
tone="info"
label="Collected"
valueText="$750 of $1,240"
/>{" $750.00 "}</UiCluster>
</div>;
}<script>
import { UiCluster, UiMeter } from "@threadlabs/looma";
</script>
<div
style="max-width: 6rem"
>
<UiCluster
gap="s"
wrap="nowrap"
> <UiMeter
size="sm"
value={750}
max={1240}
tone="info"
label="Collected"
valueText="$750 of $1,240"
/> $750.00 </UiCluster>
</div>import { Component } from "@angular/core";
import { UiCluster, UiMeter } from "@threadlabs/looma";
@Component({
selector: "example-view",
standalone: true,
imports: [UiCluster, UiMeter],
template: `
<div
style="max-width: 6rem"
>
<div
ui-cluster
[gap]="'s'"
[wrap]="'nowrap'"
> <span
ui-meter
[size]="'sm'"
[value]="750"
[max]="1240"
[tone]="'info'"
[label]="'Collected'"
[valueText]="'$750 of $1,240'"
></span> $750.00 </div>
</div>
`,
})
export default class Example {}Bounded rows with all items available
Reserve an overflow action and an always-visible trailing action within three rows. The popup contains the complete collection.
Code
<link
rel="component"
href="@threadlabs/looma/components/ui-container/ui-container.html"
>
<link
rel="component"
href="@threadlabs/looma/components/ui-stack/ui-stack.html"
>
<link
rel="component"
href="@threadlabs/looma/components/ui-cluster/ui-cluster.html"
>
<link
rel="component"
href="@threadlabs/looma/components/ui-badge/ui-badge.html"
>
<link
rel="component"
href="@threadlabs/looma/components/ui-button/ui-button.html"
>
<link
rel="component"
href="@threadlabs/looma/components/ui-popover/ui-popover.html"
>
<ui-container
fill
>
<ui-stack>
<ui-cluster
max-rows="3"
gap="xs"
>
<ui-badge>Research and discovery</ui-badge>
<ui-badge>Interface design</ui-badge>
<ui-badge>Content strategy</ui-badge>
<ui-badge>Accessibility review</ui-badge>
<ui-badge>Implementation</ui-badge>
<ui-badge>Testing and delivery</ui-badge>
<ui-button
slot="overflow"
id="bounded-all-items"
size="sm"
variant="ghost"
>All items…</ui-button>
<ui-button
slot="trailing"
size="sm"
variant="ghost"
>Add item</ui-button>
</ui-cluster>
<ui-popover
for="bounded-all-items"
role="region"
aria-label="All items"
>
<ui-cluster
gap="xs"
>
<ui-badge>Research and discovery</ui-badge>
<ui-badge>Interface design</ui-badge>
<ui-badge>Content strategy</ui-badge>
<ui-badge>Accessibility review</ui-badge>
<ui-badge>Implementation</ui-badge>
<ui-badge>Testing and delivery</ui-badge>
</ui-cluster>
</ui-popover>
</ui-stack>
</ui-container><script setup>
import { UiBadge, UiButton, UiCluster, UiContainer, UiPopover, UiStack } from "@threadlabs/looma";
</script>
<template>
<UiContainer
:fill="true"
>
<UiStack>
<UiCluster
:max-rows="3"
gap="xs"
>
<UiBadge>Research and discovery</UiBadge>
<UiBadge>Interface design</UiBadge>
<UiBadge>Content strategy</UiBadge>
<UiBadge>Accessibility review</UiBadge>
<UiBadge>Implementation</UiBadge>
<UiBadge>Testing and delivery</UiBadge>
<template #overflow>
<UiButton
id="bounded-all-items"
size="sm"
variant="ghost"
>All items…</UiButton>
</template>
<template #trailing>
<UiButton
size="sm"
variant="ghost"
>Add item</UiButton>
</template>
</UiCluster>
<UiPopover
for="bounded-all-items"
role="region"
aria-label="All items"
>
<UiCluster
gap="xs"
>
<UiBadge>Research and discovery</UiBadge>
<UiBadge>Interface design</UiBadge>
<UiBadge>Content strategy</UiBadge>
<UiBadge>Accessibility review</UiBadge>
<UiBadge>Implementation</UiBadge>
<UiBadge>Testing and delivery</UiBadge>
</UiCluster>
</UiPopover>
</UiStack>
</UiContainer>
</template>import { UiBadge, UiButton, UiCluster, UiContainer, UiPopover, UiStack } from "@threadlabs/looma";
export default function Example() {
return <UiContainer
fill
>
<UiStack>
<UiCluster
maxRows={3}
gap="xs"
slots={{
overflow: <UiButton
id="bounded-all-items"
size="sm"
variant="ghost"
>All items…</UiButton>,
trailing: <UiButton
size="sm"
variant="ghost"
>Add item</UiButton>,
}}
>
<UiBadge>Research and discovery</UiBadge>
<UiBadge>Interface design</UiBadge>
<UiBadge>Content strategy</UiBadge>
<UiBadge>Accessibility review</UiBadge>
<UiBadge>Implementation</UiBadge>
<UiBadge>Testing and delivery</UiBadge>
</UiCluster>
<UiPopover
htmlFor="bounded-all-items"
role="region"
aria-label="All items"
>
<UiCluster
gap="xs"
>
<UiBadge>Research and discovery</UiBadge>
<UiBadge>Interface design</UiBadge>
<UiBadge>Content strategy</UiBadge>
<UiBadge>Accessibility review</UiBadge>
<UiBadge>Implementation</UiBadge>
<UiBadge>Testing and delivery</UiBadge>
</UiCluster>
</UiPopover>
</UiStack>
</UiContainer>;
}/** @jsxImportSource preact */
import { UiBadge, UiButton, UiCluster, UiContainer, UiPopover, UiStack } from "@threadlabs/looma";
export default function Example() {
return <UiContainer
fill
>
<UiStack>
<UiCluster
maxRows={3}
gap="xs"
slots={{
overflow: <UiButton
id="bounded-all-items"
size="sm"
variant="ghost"
>All items…</UiButton>,
trailing: <UiButton
size="sm"
variant="ghost"
>Add item</UiButton>,
}}
>
<UiBadge>Research and discovery</UiBadge>
<UiBadge>Interface design</UiBadge>
<UiBadge>Content strategy</UiBadge>
<UiBadge>Accessibility review</UiBadge>
<UiBadge>Implementation</UiBadge>
<UiBadge>Testing and delivery</UiBadge>
</UiCluster>
<UiPopover
htmlFor="bounded-all-items"
role="region"
aria-label="All items"
>
<UiCluster
gap="xs"
>
<UiBadge>Research and discovery</UiBadge>
<UiBadge>Interface design</UiBadge>
<UiBadge>Content strategy</UiBadge>
<UiBadge>Accessibility review</UiBadge>
<UiBadge>Implementation</UiBadge>
<UiBadge>Testing and delivery</UiBadge>
</UiCluster>
</UiPopover>
</UiStack>
</UiContainer>;
}<script>
import { UiBadge, UiButton, UiCluster, UiContainer, UiPopover, UiStack } from "@threadlabs/looma";
</script>
{#snippet overflow()}
<UiButton
id="bounded-all-items"
size="sm"
variant="ghost"
>All items…</UiButton>
{/snippet}
{#snippet trailing()}
<UiButton
size="sm"
variant="ghost"
>Add item</UiButton>
{/snippet}
<UiContainer
fill
>
<UiStack>
<UiCluster
maxRows={3}
gap="xs"
slots={{ overflow, trailing }}
>
<UiBadge>Research and discovery</UiBadge>
<UiBadge>Interface design</UiBadge>
<UiBadge>Content strategy</UiBadge>
<UiBadge>Accessibility review</UiBadge>
<UiBadge>Implementation</UiBadge>
<UiBadge>Testing and delivery</UiBadge>
</UiCluster>
<UiPopover
for="bounded-all-items"
role="region"
aria-label="All items"
>
<UiCluster
gap="xs"
>
<UiBadge>Research and discovery</UiBadge>
<UiBadge>Interface design</UiBadge>
<UiBadge>Content strategy</UiBadge>
<UiBadge>Accessibility review</UiBadge>
<UiBadge>Implementation</UiBadge>
<UiBadge>Testing and delivery</UiBadge>
</UiCluster>
</UiPopover>
</UiStack>
</UiContainer>import { Component } from "@angular/core";
import { UiBadge, UiButton, UiCluster, UiContainer, UiPopover, UiStack } from "@threadlabs/looma";
@Component({
selector: "example-view",
standalone: true,
imports: [UiContainer, UiStack, UiCluster, UiBadge, UiButton, UiPopover],
template: `
<div
ui-container
[fill]="true"
>
<div
ui-stack
>
<div
ui-cluster
[maxRows]="3"
[gap]="'xs'"
>
<span
ui-badge
>Research and discovery</span>
<span
ui-badge
>Interface design</span>
<span
ui-badge
>Content strategy</span>
<span
ui-badge
>Accessibility review</span>
<span
ui-badge
>Implementation</span>
<span
ui-badge
>Testing and delivery</span>
<ng-container ngProjectAs="[slot='overflow']">
<ng-template
ui-button
[size]="'sm'"
[variant]="'ghost'"
[hnAttributes]="{ id: 'bounded-all-items' }"
>All items…</ng-template>
</ng-container>
<ng-container ngProjectAs="[slot='trailing']">
<ng-template
ui-button
[size]="'sm'"
[variant]="'ghost'"
>Add item</ng-template>
</ng-container>
</div>
<span
ui-popover
[for]="'bounded-all-items'"
role="region"
aria-label="All items"
>
<div
ui-cluster
[gap]="'xs'"
>
<span
ui-badge
>Research and discovery</span>
<span
ui-badge
>Interface design</span>
<span
ui-badge
>Content strategy</span>
<span
ui-badge
>Accessibility review</span>
<span
ui-badge
>Implementation</span>
<span
ui-badge
>Testing and delivery</span>
</div>
</span>
</div>
</div>
`,
})
export default class Example {}Theme sampler
A compact collection of real controls and a surface for judging a shared theme together.
Fellowship office
ReadySecond breakfast, sorted.
Frodo plans. Sam follows through.
Briefing at nine · Bring the maps.
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-cluster/ui-cluster.html"
>
<link
rel="component"
href="@threadlabs/looma/components/ui-text/ui-text.html"
>
<link
rel="component"
href="@threadlabs/looma/components/ui-badge/ui-badge.html"
>
<link
rel="component"
href="@threadlabs/looma/components/ui-button/ui-button.html"
>
<link
rel="component"
href="@threadlabs/looma/components/ui-icon-button/ui-icon-button.html"
>
<link
rel="component"
href="@threadlabs/looma/components/ui-icon/ui-icon.html"
>
<link
rel="component"
href="@threadlabs/looma/components/ui-input-group/ui-input-group.html"
>
<link
rel="component"
href="@threadlabs/looma/components/ui-input/ui-input.html"
>
<link
rel="component"
href="@threadlabs/looma/components/ui-checkbox/ui-checkbox.html"
>
<link
rel="component"
href="@threadlabs/looma/components/ui-switch/ui-switch.html"
>
<link
rel="component"
href="@threadlabs/looma/components/ui-radio/ui-radio.html"
>
<ui-card
treatment="expressive"
>
<ui-stack
gap="l"
>
<ui-cluster
gap="xs"
justify="between"
>
<ui-text
variant="eyebrow"
>Fellowship office</ui-text>
<ui-badge
tone="success"
>Ready</ui-badge>
</ui-cluster>
<ui-stack
gap="s"
>
<ui-text
variant="heading"
as="h3"
>Second breakfast, sorted.</ui-text>
<ui-text>Frodo plans. Sam follows through.</ui-text>
<ui-text
variant="caption"
>Briefing at nine · Bring the maps.</ui-text>
</ui-stack>
<ui-cluster>
<ui-button
variant="solid"
tone="accent"
title="Solid accent"
>Send</ui-button>
<ui-button
variant="outline"
tone="accent"
title="Outline accent"
>Save</ui-button>
<ui-button
variant="solid"
tone="neutral"
title="Solid neutral"
>File</ui-button>
<ui-button
variant="outline"
tone="neutral"
title="Outline neutral"
>Archive</ui-button>
<ui-icon-button
variant="outline"
tone="neutral"
match-button
label="Acknowledge memo"
>
<ui-icon
name="circle-check"
></ui-icon>
</ui-icon-button>
<ui-button
variant="ghost"
tone="neutral"
title="Ghost neutral"
>Later</ui-button>
</ui-cluster>
<ui-input-group>
<ui-icon
slot="prefix"
name="search"
></ui-icon>
<ui-input
aria-label="Search office"
placeholder="Search Fellowship office"
></ui-input>
</ui-input-group>
<ui-cluster>
<ui-checkbox
checked
>Notify Sam</ui-checkbox>
<ui-switch
checked
>Available</ui-switch>
<ui-radio
checked
>Daily briefing</ui-radio>
<ui-badge
tone="accent"
>Draft</ui-badge>
</ui-cluster>
</ui-stack>
</ui-card><script setup>
import {
UiBadge,
UiButton,
UiCard,
UiCheckbox,
UiCluster,
UiIcon,
UiIconButton,
UiInput,
UiInputGroup,
UiRadio,
UiStack,
UiSwitch,
UiText,
} from "@threadlabs/looma";
</script>
<template>
<UiCard
treatment="expressive"
>
<UiStack
gap="l"
>
<UiCluster
gap="xs"
justify="between"
>
<UiText
variant="eyebrow"
>Fellowship office</UiText>
<UiBadge
tone="success"
>Ready</UiBadge>
</UiCluster>
<UiStack
gap="s"
>
<UiText
variant="heading"
as="h3"
>Second breakfast, sorted.</UiText>
<UiText>Frodo plans. Sam follows through.</UiText>
<UiText
variant="caption"
>Briefing at nine · Bring the maps.</UiText>
</UiStack>
<UiCluster>
<UiButton
variant="solid"
tone="accent"
title="Solid accent"
>Send</UiButton>
<UiButton
variant="outline"
tone="accent"
title="Outline accent"
>Save</UiButton>
<UiButton
variant="solid"
tone="neutral"
title="Solid neutral"
>File</UiButton>
<UiButton
variant="outline"
tone="neutral"
title="Outline neutral"
>Archive</UiButton>
<UiIconButton
variant="outline"
tone="neutral"
:match-button="true"
label="Acknowledge memo"
>
<UiIcon
name="circle-check"
/>
</UiIconButton>
<UiButton
variant="ghost"
tone="neutral"
title="Ghost neutral"
>Later</UiButton>
</UiCluster>
<UiInputGroup>
<template #prefix>
<UiIcon
name="search"
/>
</template>
<UiInput
aria-label="Search office"
placeholder="Search Fellowship office"
/>
</UiInputGroup>
<UiCluster>
<UiCheckbox
:checked="true"
>Notify Sam</UiCheckbox>
<UiSwitch
:checked="true"
>Available</UiSwitch>
<UiRadio
:checked="true"
>Daily briefing</UiRadio>
<UiBadge
tone="accent"
>Draft</UiBadge>
</UiCluster>
</UiStack>
</UiCard>
</template>import {
UiBadge,
UiButton,
UiCard,
UiCheckbox,
UiCluster,
UiIcon,
UiIconButton,
UiInput,
UiInputGroup,
UiRadio,
UiStack,
UiSwitch,
UiText,
} from "@threadlabs/looma";
export default function Example() {
return <UiCard
treatment="expressive"
>
<UiStack
gap="l"
>
<UiCluster
gap="xs"
justify="between"
>
<UiText
variant="eyebrow"
>Fellowship office</UiText>
<UiBadge
tone="success"
>Ready</UiBadge>
</UiCluster>
<UiStack
gap="s"
>
<UiText
variant="heading"
as="h3"
>Second breakfast, sorted.</UiText>
<UiText>Frodo plans. Sam follows through.</UiText>
<UiText
variant="caption"
>Briefing at nine · Bring the maps.</UiText>
</UiStack>
<UiCluster>
<UiButton
variant="solid"
tone="accent"
title="Solid accent"
>Send</UiButton>
<UiButton
variant="outline"
tone="accent"
title="Outline accent"
>Save</UiButton>
<UiButton
variant="solid"
tone="neutral"
title="Solid neutral"
>File</UiButton>
<UiButton
variant="outline"
tone="neutral"
title="Outline neutral"
>Archive</UiButton>
<UiIconButton
variant="outline"
tone="neutral"
matchButton
label="Acknowledge memo"
>
<UiIcon
name="circle-check"
/>
</UiIconButton>
<UiButton
variant="ghost"
tone="neutral"
title="Ghost neutral"
>Later</UiButton>
</UiCluster>
<UiInputGroup
slots={{
prefix: <UiIcon
name="search"
/>,
}}
>
<UiInput
aria-label="Search office"
placeholder="Search Fellowship office"
/>
</UiInputGroup>
<UiCluster>
<UiCheckbox
checked
>Notify Sam</UiCheckbox>
<UiSwitch
checked
>Available</UiSwitch>
<UiRadio
checked
>Daily briefing</UiRadio>
<UiBadge
tone="accent"
>Draft</UiBadge>
</UiCluster>
</UiStack>
</UiCard>;
}/** @jsxImportSource preact */
import {
UiBadge,
UiButton,
UiCard,
UiCheckbox,
UiCluster,
UiIcon,
UiIconButton,
UiInput,
UiInputGroup,
UiRadio,
UiStack,
UiSwitch,
UiText,
} from "@threadlabs/looma";
export default function Example() {
return <UiCard
treatment="expressive"
>
<UiStack
gap="l"
>
<UiCluster
gap="xs"
justify="between"
>
<UiText
variant="eyebrow"
>Fellowship office</UiText>
<UiBadge
tone="success"
>Ready</UiBadge>
</UiCluster>
<UiStack
gap="s"
>
<UiText
variant="heading"
as="h3"
>Second breakfast, sorted.</UiText>
<UiText>Frodo plans. Sam follows through.</UiText>
<UiText
variant="caption"
>Briefing at nine · Bring the maps.</UiText>
</UiStack>
<UiCluster>
<UiButton
variant="solid"
tone="accent"
title="Solid accent"
>Send</UiButton>
<UiButton
variant="outline"
tone="accent"
title="Outline accent"
>Save</UiButton>
<UiButton
variant="solid"
tone="neutral"
title="Solid neutral"
>File</UiButton>
<UiButton
variant="outline"
tone="neutral"
title="Outline neutral"
>Archive</UiButton>
<UiIconButton
variant="outline"
tone="neutral"
matchButton
label="Acknowledge memo"
>
<UiIcon
name="circle-check"
/>
</UiIconButton>
<UiButton
variant="ghost"
tone="neutral"
title="Ghost neutral"
>Later</UiButton>
</UiCluster>
<UiInputGroup
slots={{
prefix: <UiIcon
name="search"
/>,
}}
>
<UiInput
aria-label="Search office"
placeholder="Search Fellowship office"
/>
</UiInputGroup>
<UiCluster>
<UiCheckbox
checked
>Notify Sam</UiCheckbox>
<UiSwitch
checked
>Available</UiSwitch>
<UiRadio
checked
>Daily briefing</UiRadio>
<UiBadge
tone="accent"
>Draft</UiBadge>
</UiCluster>
</UiStack>
</UiCard>;
}<script>
import {
UiBadge,
UiButton,
UiCard,
UiCheckbox,
UiCluster,
UiIcon,
UiIconButton,
UiInput,
UiInputGroup,
UiRadio,
UiStack,
UiSwitch,
UiText,
} from "@threadlabs/looma";
</script>
{#snippet prefix()}
<UiIcon
name="search"
/>
{/snippet}
<UiCard
treatment="expressive"
>
<UiStack
gap="l"
>
<UiCluster
gap="xs"
justify="between"
>
<UiText
variant="eyebrow"
>Fellowship office</UiText>
<UiBadge
tone="success"
>Ready</UiBadge>
</UiCluster>
<UiStack
gap="s"
>
<UiText
variant="heading"
as="h3"
>Second breakfast, sorted.</UiText>
<UiText>Frodo plans. Sam follows through.</UiText>
<UiText
variant="caption"
>Briefing at nine · Bring the maps.</UiText>
</UiStack>
<UiCluster>
<UiButton
variant="solid"
tone="accent"
title="Solid accent"
>Send</UiButton>
<UiButton
variant="outline"
tone="accent"
title="Outline accent"
>Save</UiButton>
<UiButton
variant="solid"
tone="neutral"
title="Solid neutral"
>File</UiButton>
<UiButton
variant="outline"
tone="neutral"
title="Outline neutral"
>Archive</UiButton>
<UiIconButton
variant="outline"
tone="neutral"
matchButton
label="Acknowledge memo"
>
<UiIcon
name="circle-check"
/>
</UiIconButton>
<UiButton
variant="ghost"
tone="neutral"
title="Ghost neutral"
>Later</UiButton>
</UiCluster>
<UiInputGroup
slots={{ prefix }}
>
<UiInput
aria-label="Search office"
placeholder="Search Fellowship office"
/>
</UiInputGroup>
<UiCluster>
<UiCheckbox
checked
>Notify Sam</UiCheckbox>
<UiSwitch
checked
>Available</UiSwitch>
<UiRadio
checked
>Daily briefing</UiRadio>
<UiBadge
tone="accent"
>Draft</UiBadge>
</UiCluster>
</UiStack>
</UiCard>import { Component } from "@angular/core";
import {
UiBadge,
UiButton,
UiCard,
UiCheckbox,
UiCluster,
UiIcon,
UiIconButton,
UiInput,
UiInputGroup,
UiRadio,
UiStack,
UiSwitch,
UiText,
} from "@threadlabs/looma";
@Component({
selector: "example-view",
standalone: true,
imports: [UiCard, UiStack, UiCluster, UiText, UiBadge, UiButton, UiIconButton, UiIcon, UiInputGroup, UiInput, UiCheckbox, UiSwitch, UiRadio],
template: `
<ng-template
ui-card
[treatment]="'expressive'"
>
<div
ui-stack
[gap]="'l'"
>
<div
ui-cluster
[gap]="'xs'"
[justify]="'between'"
>
<ng-template
ui-text
[variant]="'eyebrow'"
>Fellowship office</ng-template>
<span
ui-badge
[tone]="'success'"
>Ready</span>
</div>
<div
ui-stack
[gap]="'s'"
>
<ng-template
ui-text
[variant]="'heading'"
[as]="'h3'"
>Second breakfast, sorted.</ng-template>
<ng-template
ui-text
>Frodo plans. Sam follows through.</ng-template>
<ng-template
ui-text
[variant]="'caption'"
>Briefing at nine · Bring the maps.</ng-template>
</div>
<div
ui-cluster
>
<ng-template
ui-button
[variant]="'solid'"
[tone]="'accent'"
[hnAttributes]="{ title: 'Solid accent' }"
>Send</ng-template>
<ng-template
ui-button
[variant]="'outline'"
[tone]="'accent'"
[hnAttributes]="{ title: 'Outline accent' }"
>Save</ng-template>
<ng-template
ui-button
[variant]="'solid'"
[tone]="'neutral'"
[hnAttributes]="{ title: 'Solid neutral' }"
>File</ng-template>
<ng-template
ui-button
[variant]="'outline'"
[tone]="'neutral'"
[hnAttributes]="{ title: 'Outline neutral' }"
>Archive</ng-template>
<button
ui-icon-button
[variant]="'outline'"
[tone]="'neutral'"
[matchButton]="true"
[label]="'Acknowledge memo'"
>
<span
ui-icon
name="circle-check"
></span>
</button>
<ng-template
ui-button
[variant]="'ghost'"
[tone]="'neutral'"
[hnAttributes]="{ title: 'Ghost neutral' }"
>Later</ng-template>
</div>
<div
ui-input-group
>
<ng-container ngProjectAs="[slot='prefix']">
<span
ui-icon
name="search"
></span>
</ng-container>
<input
ui-input
aria-label="Search office"
placeholder="Search Fellowship office"
>
</div>
<div
ui-cluster
>
<label
ui-checkbox
[checked]="true"
>Notify Sam</label>
<label
ui-switch
[checked]="true"
>Available</label>
<label
ui-radio
[checked]="true"
>Daily briefing</label>
<span
ui-badge
[tone]="'accent'"
>Draft</span>
</div>
</div>
</ng-template>
`,
})
export default class Example {}API
Package: @threadlabs/looma · Native root: div
Attributes
| Name | Description | Property | Type | Default | Allowed values |
|---|---|---|---|---|---|
align | How items line up within each row: center (the default) aligns their middles, start their tops, end their bottoms, and stretch makes every item as tall as its row. baseline aligns the first text baselines, including multiline columns. | align | keyword | — | start, center, end, stretch, baseline |
fixed | Keeps this group at its content size inside a flex layout. | fixed | boolean | false | — |
gap | Space between items, on the spacing scale: xs is 0.5rem, s 0.75rem (also the default), m 1rem, l 1.5rem, and xl 2rem. The --ui-cluster-gap custom property, set on :root, an ancestor, or the component itself, overrides it. | gap | keyword | — | xs, s, m, l, xl |
justify | Where items sit along each row: start (the default), center, end, or between, which pushes the first and last items to the ends of the row and spreads the spare space between them, as for a title with its actions. | justify | keyword | — | start, center, end, between |
max-rows | Maximum complete visible rows when wrapping. Zero leaves the group unbounded. Supply an overflow slot with a control that opens the complete collection; extra direct items are hidden and leave keyboard navigation. The trailing slot stays visible and counts toward the row cap. | maxRows | integer | 0 | — |
padding | Optional internal space for an action group, using the shared spacing scale. xs is 0.25rem, s is 0.5rem, m is 0.75rem, and l is 1rem. | padding | keyword | none | none, xs, s, m, l |
padding-end | Optional extra space at the inline end, such as clearance beside another control. xl is 4rem. | paddingEnd | keyword | none | none, s, m, l, xl |
wrap | Whether items move onto a new row when they run out of space: wrap (the default) or nowrap, which keeps them on one row at their own sizes. Use nowrap for a small group that must read as one unit, such as a meter and its value in a table cell, so the cell grows to fit it; a whole row of actions should keep wrapping so it fits narrow screens. | wrap | keyword | — | wrap, nowrap |
Framework props
| Name | Type | Default | Allowed values |
|---|---|---|---|
align | "start" | "center" | "end" | "stretch" | "baseline" | — | start, center, end, stretch, baseline |
fixed | boolean | false | — |
gap | "xs" | "s" | "m" | "l" | "xl" | — | xs, s, m, l, xl |
justify | "start" | "center" | "end" | "between" | — | start, center, end, between |
maxRows | number | 0 | — |
padding | "none" | "xs" | "s" | "m" | "l" | none | none, xs, s, m, l |
paddingEnd | "none" | "s" | "m" | "l" | "xl" | none | none, s, m, l, xl |
wrap | "wrap" | "nowrap" | — | wrap, nowrap |
Events
| Name | Detail | Description |
|---|---|---|
overflow-change | object
| Emitted with the declared detail payload. |
Slots
| Name | Purpose |
|---|---|
default | Default child content. |
overflow | Named content for the overflow region. |
trailing | Named content for the trailing region. |
Design tokens
Set shared theme properties on a theme boundary. Component hooks apply only to their owning component.
Component tokens
| Token | Declared values | Fallback |
|---|---|---|
--ui-cluster-gap | var(--ui-space-3) | var(--ui-space-2) | var(--ui-space-4) | var(--ui-space-5) | var(--ui-space-6) | — |
Shared tokens
| Token | Fallback |
|---|---|
--ui-space-1 | — |
--ui-space-2 | — |
--ui-space-3 | — |
--ui-space-4 | — |
--ui-space-5 | — |
--ui-space-6 | — |