Reel

Contained, keyboard-focusable horizontal scrolling for cards and other repeated items.

Default

Scroll through the office desks. Small fixed-width items overflow the preview and scroll freely.

Frodo
Sam
Sméagol
Merry
Pippin
Gandalf
Aragorn
Legolas

Code

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

<ui-reel
  item-width="sm"
  aria-label="Fellowship office desks"
>
  <div>
    <ui-card
      padding="sm"
    >Frodo</ui-card>
  </div>
  <div>
    <ui-card
      padding="sm"
    >Sam</ui-card>
  </div>
  <div>
    <ui-card
      padding="sm"
    >Sméagol</ui-card>
  </div>
  <div>
    <ui-card
      padding="sm"
    >Merry</ui-card>
  </div>
  <div>
    <ui-card
      padding="sm"
    >Pippin</ui-card>
  </div>
  <div>
    <ui-card
      padding="sm"
    >Gandalf</ui-card>
  </div>
  <div>
    <ui-card
      padding="sm"
    >Aragorn</ui-card>
  </div>
  <div>
    <ui-card
      padding="sm"
    >Legolas</ui-card>
  </div>
</ui-reel>
<script setup>
  import { UiCard, UiReel } from "@threadlabs/looma";
</script>

<template>
  <UiReel
    item-width="sm"
    aria-label="Fellowship office desks"
  >
    <div>
      <UiCard
        padding="sm"
      >Frodo</UiCard>
    </div>
    <div>
      <UiCard
        padding="sm"
      >Sam</UiCard>
    </div>
    <div>
      <UiCard
        padding="sm"
      >Sméagol</UiCard>
    </div>
    <div>
      <UiCard
        padding="sm"
      >Merry</UiCard>
    </div>
    <div>
      <UiCard
        padding="sm"
      >Pippin</UiCard>
    </div>
    <div>
      <UiCard
        padding="sm"
      >Gandalf</UiCard>
    </div>
    <div>
      <UiCard
        padding="sm"
      >Aragorn</UiCard>
    </div>
    <div>
      <UiCard
        padding="sm"
      >Legolas</UiCard>
    </div>
  </UiReel>
</template>
import { UiCard, UiReel } from "@threadlabs/looma";

export default function Example() {
  return <UiReel
    itemWidth="sm"
    aria-label="Fellowship office desks"
  >
    <div>
      <UiCard
        padding="sm"
      >Frodo</UiCard>
    </div>
    <div>
      <UiCard
        padding="sm"
      >Sam</UiCard>
    </div>
    <div>
      <UiCard
        padding="sm"
      >Sméagol</UiCard>
    </div>
    <div>
      <UiCard
        padding="sm"
      >Merry</UiCard>
    </div>
    <div>
      <UiCard
        padding="sm"
      >Pippin</UiCard>
    </div>
    <div>
      <UiCard
        padding="sm"
      >Gandalf</UiCard>
    </div>
    <div>
      <UiCard
        padding="sm"
      >Aragorn</UiCard>
    </div>
    <div>
      <UiCard
        padding="sm"
      >Legolas</UiCard>
    </div>
  </UiReel>;
}
/** @jsxImportSource preact */
import { UiCard, UiReel } from "@threadlabs/looma";

export default function Example() {
  return <UiReel
    itemWidth="sm"
    aria-label="Fellowship office desks"
  >
    <div>
      <UiCard
        padding="sm"
      >Frodo</UiCard>
    </div>
    <div>
      <UiCard
        padding="sm"
      >Sam</UiCard>
    </div>
    <div>
      <UiCard
        padding="sm"
      >Sméagol</UiCard>
    </div>
    <div>
      <UiCard
        padding="sm"
      >Merry</UiCard>
    </div>
    <div>
      <UiCard
        padding="sm"
      >Pippin</UiCard>
    </div>
    <div>
      <UiCard
        padding="sm"
      >Gandalf</UiCard>
    </div>
    <div>
      <UiCard
        padding="sm"
      >Aragorn</UiCard>
    </div>
    <div>
      <UiCard
        padding="sm"
      >Legolas</UiCard>
    </div>
  </UiReel>;
}
<script>
  import { UiCard, UiReel } from "@threadlabs/looma";
</script>

<UiReel
  itemWidth="sm"
  aria-label="Fellowship office desks"
>
  <div>
    <UiCard
      padding="sm"
    >Frodo</UiCard>
  </div>
  <div>
    <UiCard
      padding="sm"
    >Sam</UiCard>
  </div>
  <div>
    <UiCard
      padding="sm"
    >Sméagol</UiCard>
  </div>
  <div>
    <UiCard
      padding="sm"
    >Merry</UiCard>
  </div>
  <div>
    <UiCard
      padding="sm"
    >Pippin</UiCard>
  </div>
  <div>
    <UiCard
      padding="sm"
    >Gandalf</UiCard>
  </div>
  <div>
    <UiCard
      padding="sm"
    >Aragorn</UiCard>
  </div>
  <div>
    <UiCard
      padding="sm"
    >Legolas</UiCard>
  </div>
</UiReel>
import { Component } from "@angular/core";
import { UiCard, UiReel } from "@threadlabs/looma";

@Component({
  selector: "example-view",
  standalone: true,
  imports: [UiReel, UiCard],
  template: `
    <div
      ui-reel
      [itemWidth]="'sm'"
      aria-label="Fellowship office desks"
    >
      <div>
        <ng-template
          ui-card
          [padding]="'sm'"
        >Frodo</ng-template>
      </div>
      <div>
        <ng-template
          ui-card
          [padding]="'sm'"
        >Sam</ng-template>
      </div>
      <div>
        <ng-template
          ui-card
          [padding]="'sm'"
        >Sméagol</ng-template>
      </div>
      <div>
        <ng-template
          ui-card
          [padding]="'sm'"
        >Merry</ng-template>
      </div>
      <div>
        <ng-template
          ui-card
          [padding]="'sm'"
        >Pippin</ng-template>
      </div>
      <div>
        <ng-template
          ui-card
          [padding]="'sm'"
        >Gandalf</ng-template>
      </div>
      <div>
        <ng-template
          ui-card
          [padding]="'sm'"
        >Aragorn</ng-template>
      </div>
      <div>
        <ng-template
          ui-card
          [padding]="'sm'"
        >Legolas</ng-template>
      </div>
    </div>
  `,
})
export default class Example {}

Gap, item width, and snap

Scroll the office desks sideways: medium-width items snap to the center when you release.

Frodo
Sam
Sméagol
Merry
Pippin
Gandalf
Aragorn
Legolas

Code

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

<ui-reel
  gap="m"
  item-width="md"
  snap="center"
  aria-label="Fellowship office desks"
>
  <div>
    <ui-card
      padding="sm"
    >Frodo</ui-card>
  </div>
  <div>
    <ui-card
      padding="sm"
    >Sam</ui-card>
  </div>
  <div>
    <ui-card
      padding="sm"
    >Sméagol</ui-card>
  </div>
  <div>
    <ui-card
      padding="sm"
    >Merry</ui-card>
  </div>
  <div>
    <ui-card
      padding="sm"
    >Pippin</ui-card>
  </div>
  <div>
    <ui-card
      padding="sm"
    >Gandalf</ui-card>
  </div>
  <div>
    <ui-card
      padding="sm"
    >Aragorn</ui-card>
  </div>
  <div>
    <ui-card
      padding="sm"
    >Legolas</ui-card>
  </div>
</ui-reel>
<script setup>
  import { UiCard, UiReel } from "@threadlabs/looma";
</script>

<template>
  <UiReel
    gap="m"
    item-width="md"
    snap="center"
    aria-label="Fellowship office desks"
  >
    <div>
      <UiCard
        padding="sm"
      >Frodo</UiCard>
    </div>
    <div>
      <UiCard
        padding="sm"
      >Sam</UiCard>
    </div>
    <div>
      <UiCard
        padding="sm"
      >Sméagol</UiCard>
    </div>
    <div>
      <UiCard
        padding="sm"
      >Merry</UiCard>
    </div>
    <div>
      <UiCard
        padding="sm"
      >Pippin</UiCard>
    </div>
    <div>
      <UiCard
        padding="sm"
      >Gandalf</UiCard>
    </div>
    <div>
      <UiCard
        padding="sm"
      >Aragorn</UiCard>
    </div>
    <div>
      <UiCard
        padding="sm"
      >Legolas</UiCard>
    </div>
  </UiReel>
</template>
import { UiCard, UiReel } from "@threadlabs/looma";

export default function Example() {
  return <UiReel
    gap="m"
    itemWidth="md"
    snap="center"
    aria-label="Fellowship office desks"
  >
    <div>
      <UiCard
        padding="sm"
      >Frodo</UiCard>
    </div>
    <div>
      <UiCard
        padding="sm"
      >Sam</UiCard>
    </div>
    <div>
      <UiCard
        padding="sm"
      >Sméagol</UiCard>
    </div>
    <div>
      <UiCard
        padding="sm"
      >Merry</UiCard>
    </div>
    <div>
      <UiCard
        padding="sm"
      >Pippin</UiCard>
    </div>
    <div>
      <UiCard
        padding="sm"
      >Gandalf</UiCard>
    </div>
    <div>
      <UiCard
        padding="sm"
      >Aragorn</UiCard>
    </div>
    <div>
      <UiCard
        padding="sm"
      >Legolas</UiCard>
    </div>
  </UiReel>;
}
/** @jsxImportSource preact */
import { UiCard, UiReel } from "@threadlabs/looma";

export default function Example() {
  return <UiReel
    gap="m"
    itemWidth="md"
    snap="center"
    aria-label="Fellowship office desks"
  >
    <div>
      <UiCard
        padding="sm"
      >Frodo</UiCard>
    </div>
    <div>
      <UiCard
        padding="sm"
      >Sam</UiCard>
    </div>
    <div>
      <UiCard
        padding="sm"
      >Sméagol</UiCard>
    </div>
    <div>
      <UiCard
        padding="sm"
      >Merry</UiCard>
    </div>
    <div>
      <UiCard
        padding="sm"
      >Pippin</UiCard>
    </div>
    <div>
      <UiCard
        padding="sm"
      >Gandalf</UiCard>
    </div>
    <div>
      <UiCard
        padding="sm"
      >Aragorn</UiCard>
    </div>
    <div>
      <UiCard
        padding="sm"
      >Legolas</UiCard>
    </div>
  </UiReel>;
}
<script>
  import { UiCard, UiReel } from "@threadlabs/looma";
</script>

<UiReel
  gap="m"
  itemWidth="md"
  snap="center"
  aria-label="Fellowship office desks"
>
  <div>
    <UiCard
      padding="sm"
    >Frodo</UiCard>
  </div>
  <div>
    <UiCard
      padding="sm"
    >Sam</UiCard>
  </div>
  <div>
    <UiCard
      padding="sm"
    >Sméagol</UiCard>
  </div>
  <div>
    <UiCard
      padding="sm"
    >Merry</UiCard>
  </div>
  <div>
    <UiCard
      padding="sm"
    >Pippin</UiCard>
  </div>
  <div>
    <UiCard
      padding="sm"
    >Gandalf</UiCard>
  </div>
  <div>
    <UiCard
      padding="sm"
    >Aragorn</UiCard>
  </div>
  <div>
    <UiCard
      padding="sm"
    >Legolas</UiCard>
  </div>
</UiReel>
import { Component } from "@angular/core";
import { UiCard, UiReel } from "@threadlabs/looma";

@Component({
  selector: "example-view",
  standalone: true,
  imports: [UiReel, UiCard],
  template: `
    <div
      ui-reel
      [gap]="'m'"
      [itemWidth]="'md'"
      [snap]="'center'"
      aria-label="Fellowship office desks"
    >
      <div>
        <ng-template
          ui-card
          [padding]="'sm'"
        >Frodo</ng-template>
      </div>
      <div>
        <ng-template
          ui-card
          [padding]="'sm'"
        >Sam</ng-template>
      </div>
      <div>
        <ng-template
          ui-card
          [padding]="'sm'"
        >Sméagol</ng-template>
      </div>
      <div>
        <ng-template
          ui-card
          [padding]="'sm'"
        >Merry</ng-template>
      </div>
      <div>
        <ng-template
          ui-card
          [padding]="'sm'"
        >Pippin</ng-template>
      </div>
      <div>
        <ng-template
          ui-card
          [padding]="'sm'"
        >Gandalf</ng-template>
      </div>
      <div>
        <ng-template
          ui-card
          [padding]="'sm'"
        >Aragorn</ng-template>
      </div>
      <div>
        <ng-template
          ui-card
          [padding]="'sm'"
        >Legolas</ng-template>
      </div>
    </div>
  `,
})
export default class Example {}

API

Package: @threadlabs/looma · Native root: div

Attributes

NameDescriptionPropertyTypeDefaultAllowed values
gapSpace between items, on the spacing scale: xs is 0.5rem, s 0.75rem, m 1rem (also the default), l 1.5rem, and xl 2rem. The --ui-reel-gap custom property, set on :root, an ancestor, or the component itself, overrides it.gapkeyword—xs, s, m, l, xl
item-widthA fixed width for every item: sm is 12rem, md is 16rem, and lg is 20rem. Unset, each item keeps its content's width. Items never shrink to fit, so the reel scrolls sideways once they overflow.itemWidthkeyword—sm, md, lg
snapSnaps scrolling to items: start comes to rest with an item's start edge at the reel's start, and center with an item centred in the reel. Unset, the reel scrolls freely. Wrap a component item in a plain element when snapping: the wrapper receives snap alignment while the component keeps ownership of its root styles.snapkeyword—start, center

Framework props

NameTypeDefaultAllowed values
gap"xs" | "s" | "m" | "l" | "xl"—xs, s, m, l, xl
itemWidth"sm" | "md" | "lg"—sm, md, lg
snap"start" | "center"—start, center

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-reel-gapvar(--ui-space-4) | var(--ui-space-2) | var(--ui-space-3) | var(--ui-space-5) | var(--ui-space-6)—
--ui-reel-item-widthmax-content | 12rem | 16rem | 20rem—

Shared tokens

TokenFallback
--ui-scroll-fade-size24px
--ui-space-2—
--ui-space-3—
--ui-space-4—
--ui-space-5—
--ui-space-6—