List

A list of items: pages, people, sites, results.

Native list items

Use li for ordinary content. The list owns the spacing between its items.

  • Roadmap
  • Onboarding
  • Glossary

Code

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

<ui-list
  aria-label="Recently updated"
>
  <li>Roadmap</li>
  <li>Onboarding</li>
  <li>Glossary</li>
</ui-list>
<script setup>
  import { UiList } from "@threadlabs/looma";
</script>

<template>
  <UiList
    aria-label="Recently updated"
  >
    <li>Roadmap</li>
    <li>Onboarding</li>
    <li>Glossary</li>
  </UiList>
</template>
import { UiList } from "@threadlabs/looma";

export default function Example() {
  return <UiList
    aria-label="Recently updated"
  >
    <li>Roadmap</li>
    <li>Onboarding</li>
    <li>Glossary</li>
  </UiList>;
}
/** @jsxImportSource preact */
import { UiList } from "@threadlabs/looma";

export default function Example() {
  return <UiList
    aria-label="Recently updated"
  >
    <li>Roadmap</li>
    <li>Onboarding</li>
    <li>Glossary</li>
  </UiList>;
}
<script>
  import { UiList } from "@threadlabs/looma";
</script>

<UiList
  aria-label="Recently updated"
>
  <li>Roadmap</li>
  <li>Onboarding</li>
  <li>Glossary</li>
</UiList>
import { Component } from "@angular/core";
import { UiList } from "@threadlabs/looma";

@Component({
  selector: "example-view",
  standalone: true,
  imports: [UiList],
  template: `
    <ng-template
      ui-list
      [hnAttributes]="{ 'aria-label': 'Recently updated' }"
    >
      <li>Roadmap</li>
      <li>Onboarding</li>
      <li>Glossary</li>
    </ng-template>
  `,
})
export default class Example {}

Grid

layout="grid" sets card items in as many columns as fit, each at least 14rem wide.

Code

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

<ui-list
  layout="grid"
  aria-label="Pinned"
>
  <ui-list-item
    variant="card"
  ><a
      href="#roadmap"
    >Roadmap</a><span
      slot="description"
    >Planning</span></ui-list-item>
  <ui-list-item
    variant="card"
  ><a
      href="#onboarding"
    >Onboarding</a><span
      slot="description"
    >People</span></ui-list-item>
  <ui-list-item
    variant="card"
  ><a
      href="#glossary"
    >Glossary</a><span
      slot="description"
    >Reference</span></ui-list-item>
</ui-list>
<script setup>
  import { UiList, UiListItem } from "@threadlabs/looma";
</script>

<template>
  <UiList
    layout="grid"
    aria-label="Pinned"
  >
    <UiListItem
      variant="card"
    >
      <a
        href="#roadmap"
      >Roadmap</a>
      <template #description>
        <span>Planning</span>
      </template>
    </UiListItem>
    <UiListItem
      variant="card"
    >
      <a
        href="#onboarding"
      >Onboarding</a>
      <template #description>
        <span>People</span>
      </template>
    </UiListItem>
    <UiListItem
      variant="card"
    >
      <a
        href="#glossary"
      >Glossary</a>
      <template #description>
        <span>Reference</span>
      </template>
    </UiListItem>
  </UiList>
</template>
import { UiList, UiListItem } from "@threadlabs/looma";

export default function Example() {
  return <UiList
    layout="grid"
    aria-label="Pinned"
  >
    <UiListItem
      variant="card"
      slots={{
        description: <span>Planning</span>,
      }}
    ><a
        href="#roadmap"
      >Roadmap</a></UiListItem>
    <UiListItem
      variant="card"
      slots={{
        description: <span>People</span>,
      }}
    ><a
        href="#onboarding"
      >Onboarding</a></UiListItem>
    <UiListItem
      variant="card"
      slots={{
        description: <span>Reference</span>,
      }}
    ><a
        href="#glossary"
      >Glossary</a></UiListItem>
  </UiList>;
}
/** @jsxImportSource preact */
import { UiList, UiListItem } from "@threadlabs/looma";

export default function Example() {
  return <UiList
    layout="grid"
    aria-label="Pinned"
  >
    <UiListItem
      variant="card"
      slots={{
        description: <span>Planning</span>,
      }}
    ><a
        href="#roadmap"
      >Roadmap</a></UiListItem>
    <UiListItem
      variant="card"
      slots={{
        description: <span>People</span>,
      }}
    ><a
        href="#onboarding"
      >Onboarding</a></UiListItem>
    <UiListItem
      variant="card"
      slots={{
        description: <span>Reference</span>,
      }}
    ><a
        href="#glossary"
      >Glossary</a></UiListItem>
  </UiList>;
}
<script>
  import { UiList, UiListItem } from "@threadlabs/looma";
</script>

{#snippet description()}
  <span>Planning</span>
{/snippet}

{#snippet listItemDescription()}
  <span>People</span>
{/snippet}

{#snippet listItemDescription2()}
  <span>Reference</span>
{/snippet}

<UiList
  layout="grid"
  aria-label="Pinned"
>
  <UiListItem
    variant="card"
    slots={{ description }}
  ><a
      href="#roadmap"
    >Roadmap</a></UiListItem>
  <UiListItem
    variant="card"
    slots={{ description: listItemDescription }}
  ><a
      href="#onboarding"
    >Onboarding</a></UiListItem>
  <UiListItem
    variant="card"
    slots={{ description: listItemDescription2 }}
  ><a
      href="#glossary"
    >Glossary</a></UiListItem>
</UiList>
import { Component } from "@angular/core";
import { UiList, UiListItem } from "@threadlabs/looma";

@Component({
  selector: "example-view",
  standalone: true,
  imports: [UiList, UiListItem],
  template: `
    <ng-template
      ui-list
      [layout]="'grid'"
      [hnAttributes]="{ 'aria-label': 'Pinned' }"
    >
      <li
        ui-list-item
        [variant]="'card'"
      >
        <a
          href="#roadmap"
        >Roadmap</a>
        <ng-container ngProjectAs="[slot='description']">
          <span>Planning</span>
        </ng-container>
      </li>
      <li
        ui-list-item
        [variant]="'card'"
      >
        <a
          href="#onboarding"
        >Onboarding</a>
        <ng-container ngProjectAs="[slot='description']">
          <span>People</span>
        </ng-container>
      </li>
      <li
        ui-list-item
        [variant]="'card'"
      >
        <a
          href="#glossary"
        >Glossary</a>
        <ng-container ngProjectAs="[slot='description']">
          <span>Reference</span>
        </ng-container>
      </li>
    </ng-template>
  `,
})
export default class Example {}

Compact

density="compact" sets a short list of facts close together, such as what a plan includes.

  • Up to 50 people
  • Multiple workspaces
  • Multi-document review

Code

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

<ui-list
  density="compact"
  aria-label="Pro includes"
>
  <ui-list-item>
    <ui-icon
      slot="leading"
      name="circle-check"
    ></ui-icon>
    Up to 50 people
  </ui-list-item>
  <ui-list-item>
    <ui-icon
      slot="leading"
      name="circle-check"
    ></ui-icon>
    Multiple workspaces
  </ui-list-item>
  <ui-list-item>
    <ui-icon
      slot="leading"
      name="circle-check"
    ></ui-icon>
    Multi-document review
  </ui-list-item>
</ui-list>
<script setup>
  import { UiIcon, UiList, UiListItem } from "@threadlabs/looma";
</script>

<template>
  <UiList
    density="compact"
    aria-label="Pro includes"
  >
    <UiListItem><template #leading>
        <UiIcon
          name="circle-check"
        />
      </template>Up to 50 people</UiListItem>
    <UiListItem><template #leading>
        <UiIcon
          name="circle-check"
        />
      </template>Multiple workspaces</UiListItem>
    <UiListItem><template #leading>
        <UiIcon
          name="circle-check"
        />
      </template>Multi-document review</UiListItem>
  </UiList>
</template>
import { UiIcon, UiList, UiListItem } from "@threadlabs/looma";

export default function Example() {
  return <UiList
    density="compact"
    aria-label="Pro includes"
  >
    <UiListItem
      slots={{
        leading: <UiIcon
          name="circle-check"
        />,
      }}
    >Up to 50 people</UiListItem>
    <UiListItem
      slots={{
        leading: <UiIcon
          name="circle-check"
        />,
      }}
    >Multiple workspaces</UiListItem>
    <UiListItem
      slots={{
        leading: <UiIcon
          name="circle-check"
        />,
      }}
    >Multi-document review</UiListItem>
  </UiList>;
}
/** @jsxImportSource preact */
import { UiIcon, UiList, UiListItem } from "@threadlabs/looma";

export default function Example() {
  return <UiList
    density="compact"
    aria-label="Pro includes"
  >
    <UiListItem
      slots={{
        leading: <UiIcon
          name="circle-check"
        />,
      }}
    >Up to 50 people</UiListItem>
    <UiListItem
      slots={{
        leading: <UiIcon
          name="circle-check"
        />,
      }}
    >Multiple workspaces</UiListItem>
    <UiListItem
      slots={{
        leading: <UiIcon
          name="circle-check"
        />,
      }}
    >Multi-document review</UiListItem>
  </UiList>;
}
<script>
  import { UiIcon, UiList, UiListItem } from "@threadlabs/looma";
</script>

{#snippet leading()}
  <UiIcon
    name="circle-check"
  />
{/snippet}

{#snippet listItemLeading()}
  <UiIcon
    name="circle-check"
  />
{/snippet}

{#snippet listItemLeading2()}
  <UiIcon
    name="circle-check"
  />
{/snippet}

<UiList
  density="compact"
  aria-label="Pro includes"
>
  <UiListItem
    slots={{ leading }}
  >Up to 50 people</UiListItem>
  <UiListItem
    slots={{ leading: listItemLeading }}
  >Multiple workspaces</UiListItem>
  <UiListItem
    slots={{ leading: listItemLeading2 }}
  >Multi-document review</UiListItem>
</UiList>
import { Component } from "@angular/core";
import { UiIcon, UiList, UiListItem } from "@threadlabs/looma";

@Component({
  selector: "example-view",
  standalone: true,
  imports: [UiList, UiListItem, UiIcon],
  template: `
    <ng-template
      ui-list
      [density]="'compact'"
      [hnAttributes]="{ 'aria-label': 'Pro includes' }"
    >
      <li
        ui-list-item
      ><ng-container ngProjectAs="[slot='leading']">
          <span
            ui-icon
            name="circle-check"
          ></span>
        </ng-container>Up to 50 people</li>
      <li
        ui-list-item
      ><ng-container ngProjectAs="[slot='leading']">
          <span
            ui-icon
            name="circle-check"
          ></span>
        </ng-container>Multiple workspaces</li>
      <li
        ui-list-item
      ><ng-container ngProjectAs="[slot='leading']">
          <span
            ui-icon
            name="circle-check"
          ></span>
        </ng-container>Multi-document review</li>
    </ng-template>
  `,
})
export default class Example {}

Structured rows

Use ui-list-item when a row needs leading, title, description, trailing, or full-row link behavior.

Code

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

<ui-list
  aria-label="Recently updated"
>
  <ui-list-item><a
      href="#roadmap"
    >Roadmap</a><span
      slot="description"
    >Planning · Edited 2h ago</span></ui-list-item>
  <ui-list-item><a
      href="#onboarding"
    >Onboarding</a><span
      slot="description"
    >People · Edited yesterday</span></ui-list-item>
  <ui-list-item><a
      href="#glossary"
    >Glossary</a><span
      slot="description"
    >Edited 3 days ago</span></ui-list-item>
</ui-list>
<script setup>
  import { UiList, UiListItem } from "@threadlabs/looma";
</script>

<template>
  <UiList
    aria-label="Recently updated"
  >
    <UiListItem>
      <a
        href="#roadmap"
      >Roadmap</a>
      <template #description>
        <span>Planning · Edited 2h ago</span>
      </template>
    </UiListItem>
    <UiListItem>
      <a
        href="#onboarding"
      >Onboarding</a>
      <template #description>
        <span>People · Edited yesterday</span>
      </template>
    </UiListItem>
    <UiListItem>
      <a
        href="#glossary"
      >Glossary</a>
      <template #description>
        <span>Edited 3 days ago</span>
      </template>
    </UiListItem>
  </UiList>
</template>
import { UiList, UiListItem } from "@threadlabs/looma";

export default function Example() {
  return <UiList
    aria-label="Recently updated"
  >
    <UiListItem
      slots={{
        description: <span>Planning · Edited 2h ago</span>,
      }}
    ><a
        href="#roadmap"
      >Roadmap</a></UiListItem>
    <UiListItem
      slots={{
        description: <span>People · Edited yesterday</span>,
      }}
    ><a
        href="#onboarding"
      >Onboarding</a></UiListItem>
    <UiListItem
      slots={{
        description: <span>Edited 3 days ago</span>,
      }}
    ><a
        href="#glossary"
      >Glossary</a></UiListItem>
  </UiList>;
}
/** @jsxImportSource preact */
import { UiList, UiListItem } from "@threadlabs/looma";

export default function Example() {
  return <UiList
    aria-label="Recently updated"
  >
    <UiListItem
      slots={{
        description: <span>Planning · Edited 2h ago</span>,
      }}
    ><a
        href="#roadmap"
      >Roadmap</a></UiListItem>
    <UiListItem
      slots={{
        description: <span>People · Edited yesterday</span>,
      }}
    ><a
        href="#onboarding"
      >Onboarding</a></UiListItem>
    <UiListItem
      slots={{
        description: <span>Edited 3 days ago</span>,
      }}
    ><a
        href="#glossary"
      >Glossary</a></UiListItem>
  </UiList>;
}
<script>
  import { UiList, UiListItem } from "@threadlabs/looma";
</script>

{#snippet description()}
  <span>Planning · Edited 2h ago</span>
{/snippet}

{#snippet listItemDescription()}
  <span>People · Edited yesterday</span>
{/snippet}

{#snippet listItemDescription2()}
  <span>Edited 3 days ago</span>
{/snippet}

<UiList
  aria-label="Recently updated"
>
  <UiListItem
    slots={{ description }}
  ><a
      href="#roadmap"
    >Roadmap</a></UiListItem>
  <UiListItem
    slots={{ description: listItemDescription }}
  ><a
      href="#onboarding"
    >Onboarding</a></UiListItem>
  <UiListItem
    slots={{ description: listItemDescription2 }}
  ><a
      href="#glossary"
    >Glossary</a></UiListItem>
</UiList>
import { Component } from "@angular/core";
import { UiList, UiListItem } from "@threadlabs/looma";

@Component({
  selector: "example-view",
  standalone: true,
  imports: [UiList, UiListItem],
  template: `
    <ng-template
      ui-list
      [hnAttributes]="{ 'aria-label': 'Recently updated' }"
    >
      <li
        ui-list-item
      >
        <a
          href="#roadmap"
        >Roadmap</a>
        <ng-container ngProjectAs="[slot='description']">
          <span>Planning · Edited 2h ago</span>
        </ng-container>
      </li>
      <li
        ui-list-item
      >
        <a
          href="#onboarding"
        >Onboarding</a>
        <ng-container ngProjectAs="[slot='description']">
          <span>People · Edited yesterday</span>
        </ng-container>
      </li>
      <li
        ui-list-item
      >
        <a
          href="#glossary"
        >Glossary</a>
        <ng-container ngProjectAs="[slot='description']">
          <span>Edited 3 days ago</span>
        </ng-container>
      </li>
    </ng-template>
  `,
})
export default class Example {}

Native cards

A direct li with class="card" gets the same outlined surface in the grid layout.

  • Roadmap
  • Onboarding
  • Glossary

Code

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

<ui-list
  layout="grid"
  aria-label="Highlights"
>
  <li
    class="card"
  >Roadmap</li>
  <li
    class="card"
  >Onboarding</li>
  <li
    class="card"
  >Glossary</li>
</ui-list>
<script setup>
  import { UiList } from "@threadlabs/looma";
</script>

<template>
  <UiList
    layout="grid"
    aria-label="Highlights"
  >
    <li
      class="card"
    >Roadmap</li>
    <li
      class="card"
    >Onboarding</li>
    <li
      class="card"
    >Glossary</li>
  </UiList>
</template>
import { UiList } from "@threadlabs/looma";

export default function Example() {
  return <UiList
    layout="grid"
    aria-label="Highlights"
  >
    <li
      className="card"
    >Roadmap</li>
    <li
      className="card"
    >Onboarding</li>
    <li
      className="card"
    >Glossary</li>
  </UiList>;
}
/** @jsxImportSource preact */
import { UiList } from "@threadlabs/looma";

export default function Example() {
  return <UiList
    layout="grid"
    aria-label="Highlights"
  >
    <li
      className="card"
    >Roadmap</li>
    <li
      className="card"
    >Onboarding</li>
    <li
      className="card"
    >Glossary</li>
  </UiList>;
}
<script>
  import { UiList } from "@threadlabs/looma";
</script>

<UiList
  layout="grid"
  aria-label="Highlights"
>
  <li
    class="card"
  >Roadmap</li>
  <li
    class="card"
  >Onboarding</li>
  <li
    class="card"
  >Glossary</li>
</UiList>
import { Component } from "@angular/core";
import { UiList } from "@threadlabs/looma";

@Component({
  selector: "example-view",
  standalone: true,
  imports: [UiList],
  template: `
    <ng-template
      ui-list
      [layout]="'grid'"
      [hnAttributes]="{ 'aria-label': 'Highlights' }"
    >
      <li
        class="card"
      >Roadmap</li>
      <li
        class="card"
      >Onboarding</li>
      <li
        class="card"
      >Glossary</li>
    </ng-template>
  `,
})
export default class Example {}

Bullets

markers="disc" shows native bullets for a list of ordinary li elements.

  • Draft the outline
  • Review the content
  • Publish the page

Code

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

<ui-list
  markers="disc"
>
  <li>Draft the outline</li>
  <li>Review the content</li>
  <li>Publish the page</li>
</ui-list>
<script setup>
  import { UiList } from "@threadlabs/looma";
</script>

<template>
  <UiList
    markers="disc"
  >
    <li>Draft the outline</li>
    <li>Review the content</li>
    <li>Publish the page</li>
  </UiList>
</template>
import { UiList } from "@threadlabs/looma";

export default function Example() {
  return <UiList
    markers="disc"
  >
    <li>Draft the outline</li>
    <li>Review the content</li>
    <li>Publish the page</li>
  </UiList>;
}
/** @jsxImportSource preact */
import { UiList } from "@threadlabs/looma";

export default function Example() {
  return <UiList
    markers="disc"
  >
    <li>Draft the outline</li>
    <li>Review the content</li>
    <li>Publish the page</li>
  </UiList>;
}
<script>
  import { UiList } from "@threadlabs/looma";
</script>

<UiList
  markers="disc"
>
  <li>Draft the outline</li>
  <li>Review the content</li>
  <li>Publish the page</li>
</UiList>
import { Component } from "@angular/core";
import { UiList } from "@threadlabs/looma";

@Component({
  selector: "example-view",
  standalone: true,
  imports: [UiList],
  template: `
    <ng-template
      ui-list
      [markers]="'disc'"
    >
      <li>Draft the outline</li>
      <li>Review the content</li>
      <li>Publish the page</li>
    </ng-template>
  `,
})
export default class Example {}

Numbered steps

as="ol" uses a native ordered list and shows its numbers.

  1. Draft the outline
  2. Review the content
  3. Publish the page

Code

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

<ui-list
  as="ol"
>
  <li>Draft the outline</li>
  <li>Review the content</li>
  <li>Publish the page</li>
</ui-list>
<script setup>
  import { UiList } from "@threadlabs/looma";
</script>

<template>
  <UiList
    as="ol"
  >
    <li>Draft the outline</li>
    <li>Review the content</li>
    <li>Publish the page</li>
  </UiList>
</template>
import { UiList } from "@threadlabs/looma";

export default function Example() {
  return <UiList
    as="ol"
  >
    <li>Draft the outline</li>
    <li>Review the content</li>
    <li>Publish the page</li>
  </UiList>;
}
/** @jsxImportSource preact */
import { UiList } from "@threadlabs/looma";

export default function Example() {
  return <UiList
    as="ol"
  >
    <li>Draft the outline</li>
    <li>Review the content</li>
    <li>Publish the page</li>
  </UiList>;
}
<script>
  import { UiList } from "@threadlabs/looma";
</script>

<UiList
  as="ol"
>
  <li>Draft the outline</li>
  <li>Review the content</li>
  <li>Publish the page</li>
</UiList>
import { Component } from "@angular/core";
import { UiList } from "@threadlabs/looma";

@Component({
  selector: "example-view",
  standalone: true,
  imports: [UiList],
  template: `
    <ng-template
      ui-list
      [as]="'ol'"
    >
      <li>Draft the outline</li>
      <li>Review the content</li>
      <li>Publish the page</li>
    </ng-template>
  `,
})
export default class Example {}

Link

A link in the title is the whole item. Its icon and padding follow the link, and the item takes the link's hover surface and focus ring.

  • Roadmap Planning · Edited 2h ago

Code

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

<ui-list
  aria-label="Pages"
>
  <ui-list-item>
    <ui-icon
      slot="leading"
      name="file-text"
    ></ui-icon>
    <a
      href="#roadmap"
    >Roadmap</a>
    <span
      slot="description"
    >Planning · Edited 2h ago</span>
  </ui-list-item>
</ui-list>
<script setup>
  import { UiIcon, UiList, UiListItem } from "@threadlabs/looma";
</script>

<template>
  <UiList
    aria-label="Pages"
  >
    <UiListItem>
      <template #leading>
        <UiIcon
          name="file-text"
        />
      </template>
      <a
        href="#roadmap"
      >Roadmap</a>
      <template #description>
        <span>Planning · Edited 2h ago</span>
      </template>
    </UiListItem>
  </UiList>
</template>
import { UiIcon, UiList, UiListItem } from "@threadlabs/looma";

export default function Example() {
  return <UiList
    aria-label="Pages"
  >
    <UiListItem
      slots={{
        leading: <UiIcon
          name="file-text"
        />,
        description: <span>Planning · Edited 2h ago</span>,
      }}
    >{" "}{" "}<a
        href="#roadmap"
      >Roadmap</a>{" "}{" "}</UiListItem>
  </UiList>;
}
/** @jsxImportSource preact */
import { UiIcon, UiList, UiListItem } from "@threadlabs/looma";

export default function Example() {
  return <UiList
    aria-label="Pages"
  >
    <UiListItem
      slots={{
        leading: <UiIcon
          name="file-text"
        />,
        description: <span>Planning · Edited 2h ago</span>,
      }}
    >{" "}{" "}<a
        href="#roadmap"
      >Roadmap</a>{" "}{" "}</UiListItem>
  </UiList>;
}
<script>
  import { UiIcon, UiList, UiListItem } from "@threadlabs/looma";
</script>

{#snippet leading()}
  <UiIcon
    name="file-text"
  />
{/snippet}

{#snippet description()}
  <span>Planning · Edited 2h ago</span>
{/snippet}

<UiList
  aria-label="Pages"
>
  <UiListItem
    slots={{ leading, description }}
  >  <a
      href="#roadmap"
    >Roadmap</a>  </UiListItem>
</UiList>
import { Component } from "@angular/core";
import { UiIcon, UiList, UiListItem } from "@threadlabs/looma";

@Component({
  selector: "example-view",
  standalone: true,
  imports: [UiList, UiListItem, UiIcon],
  template: `
    <ng-template
      ui-list
      [hnAttributes]="{ 'aria-label': 'Pages' }"
    >
      <li
        ui-list-item
      >
        <ng-container ngProjectAs="[slot='leading']">
          <span
            ui-icon
            name="file-text"
          ></span>
        </ng-container>
        <a
          href="#roadmap"
        >Roadmap</a>
        <ng-container ngProjectAs="[slot='description']">
          <span>Planning · Edited 2h ago</span>
        </ng-container>
      </li>
    </ng-template>
  `,
})
export default class Example {}

Trailing control

A trailing control stays its own: pressing Restore does not follow a title link. An item with no link has no hover surface.

  • Old roadmap Deleted 3 days ago

Code

<link
  rel="component"
  href="@threadlabs/looma/components/ui-list/ui-list.html"
>
<link
  rel="component"
  href="@threadlabs/looma/components/ui-list-item/ui-list-item.html"
>
<link
  rel="component"
  href="@threadlabs/looma/components/ui-icon/ui-icon.html"
>
<link
  rel="component"
  href="@threadlabs/looma/components/ui-button/ui-button.html"
>

<ui-list
  aria-label="Deleted pages"
>
  <ui-list-item>
    <ui-icon
      slot="leading"
      name="file-text"
    ></ui-icon>
    Old roadmap
    <span
      slot="description"
    >Deleted 3 days ago</span>
    <ui-button
      slot="trailing"
      size="sm"
      variant="outline"
    >Restore</ui-button>
  </ui-list-item>
</ui-list>
<script setup>
  import { UiButton, UiIcon, UiList, UiListItem } from "@threadlabs/looma";
</script>

<template>
  <UiList
    aria-label="Deleted pages"
  >
    <UiListItem> <template #leading>
        <UiIcon
          name="file-text"
        />
      </template> Old roadmap <template #description>
        <span>Deleted 3 days ago</span>
      </template> <template #trailing>
        <UiButton
          size="sm"
          variant="outline"
        >Restore</UiButton>
      </template> </UiListItem>
  </UiList>
</template>
import { UiButton, UiIcon, UiList, UiListItem } from "@threadlabs/looma";

export default function Example() {
  return <UiList
    aria-label="Deleted pages"
  >
    <UiListItem
      slots={{
        leading: <UiIcon
          name="file-text"
        />,
        description: <span>Deleted 3 days ago</span>,
        trailing: <UiButton
          size="sm"
          variant="outline"
        >Restore</UiButton>,
      }}
    >{" "}{" Old roadmap "}{" "}{" "}</UiListItem>
  </UiList>;
}
/** @jsxImportSource preact */
import { UiButton, UiIcon, UiList, UiListItem } from "@threadlabs/looma";

export default function Example() {
  return <UiList
    aria-label="Deleted pages"
  >
    <UiListItem
      slots={{
        leading: <UiIcon
          name="file-text"
        />,
        description: <span>Deleted 3 days ago</span>,
        trailing: <UiButton
          size="sm"
          variant="outline"
        >Restore</UiButton>,
      }}
    >{" "}{" Old roadmap "}{" "}{" "}</UiListItem>
  </UiList>;
}
<script>
  import { UiButton, UiIcon, UiList, UiListItem } from "@threadlabs/looma";
</script>

{#snippet leading()}
  <UiIcon
    name="file-text"
  />
{/snippet}

{#snippet description()}
  <span>Deleted 3 days ago</span>
{/snippet}

{#snippet trailing()}
  <UiButton
    size="sm"
    variant="outline"
  >Restore</UiButton>
{/snippet}

<UiList
  aria-label="Deleted pages"
>
  <UiListItem
    slots={{ leading, description, trailing }}
  >  Old roadmap   </UiListItem>
</UiList>
import { Component } from "@angular/core";
import { UiButton, UiIcon, UiList, UiListItem } from "@threadlabs/looma";

@Component({
  selector: "example-view",
  standalone: true,
  imports: [UiList, UiListItem, UiIcon, UiButton],
  template: `
    <ng-template
      ui-list
      [hnAttributes]="{ 'aria-label': 'Deleted pages' }"
    >
      <li
        ui-list-item
      > <ng-container ngProjectAs="[slot='leading']">
          <span
            ui-icon
            name="file-text"
          ></span>
        </ng-container> Old roadmap <ng-container ngProjectAs="[slot='description']">
          <span>Deleted 3 days ago</span>
        </ng-container> <ng-container ngProjectAs="[slot='trailing']">
          <ng-template
            ui-button
            [size]="'sm'"
            [variant]="'outline'"
          >Restore</ng-template>
        </ng-container> </li>
    </ng-template>
  `,
})
export default class Example {}

Card and current

variant="card" draws the same parts on a bordered surface, for a grid. current marks the item you are in.

  • Acme acme.example.com Current
  • Globex globex.example.com

Code

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

<ui-list
  layout="grid"
  aria-label="Sites"
>
  <ui-list-item
    variant="card"
    current
  >
    <a
      href="#acme"
    >Acme</a>
    <span
      slot="description"
    >acme.example.com</span>
    <ui-badge
      slot="trailing"
    >Current</ui-badge>
  </ui-list-item>
  <ui-list-item
    variant="card"
  ><a
      href="#globex"
    >Globex</a><span
      slot="description"
    >globex.example.com</span></ui-list-item>
</ui-list>
<script setup>
  import { UiBadge, UiList, UiListItem } from "@threadlabs/looma";
</script>

<template>
  <UiList
    layout="grid"
    aria-label="Sites"
  >
    <UiListItem
      variant="card"
      :current="true"
    >
      <a
        href="#acme"
      >Acme</a>
      <template #description>
        <span>acme.example.com</span>
      </template>
      <template #trailing>
        <UiBadge>Current</UiBadge>
      </template>
    </UiListItem>
    <UiListItem
      variant="card"
    >
      <a
        href="#globex"
      >Globex</a>
      <template #description>
        <span>globex.example.com</span>
      </template>
    </UiListItem>
  </UiList>
</template>
import { UiBadge, UiList, UiListItem } from "@threadlabs/looma";

export default function Example() {
  return <UiList
    layout="grid"
    aria-label="Sites"
  >
    <UiListItem
      variant="card"
      current
      slots={{
        description: <span>acme.example.com</span>,
        trailing: <UiBadge>Current</UiBadge>,
      }}
    ><a
        href="#acme"
      >Acme</a></UiListItem>
    <UiListItem
      variant="card"
      slots={{
        description: <span>globex.example.com</span>,
      }}
    ><a
        href="#globex"
      >Globex</a></UiListItem>
  </UiList>;
}
/** @jsxImportSource preact */
import { UiBadge, UiList, UiListItem } from "@threadlabs/looma";

export default function Example() {
  return <UiList
    layout="grid"
    aria-label="Sites"
  >
    <UiListItem
      variant="card"
      current
      slots={{
        description: <span>acme.example.com</span>,
        trailing: <UiBadge>Current</UiBadge>,
      }}
    ><a
        href="#acme"
      >Acme</a></UiListItem>
    <UiListItem
      variant="card"
      slots={{
        description: <span>globex.example.com</span>,
      }}
    ><a
        href="#globex"
      >Globex</a></UiListItem>
  </UiList>;
}
<script>
  import { UiBadge, UiList, UiListItem } from "@threadlabs/looma";
</script>

{#snippet description()}
  <span>acme.example.com</span>
{/snippet}

{#snippet trailing()}
  <UiBadge>Current</UiBadge>
{/snippet}

{#snippet listItemDescription()}
  <span>globex.example.com</span>
{/snippet}

<UiList
  layout="grid"
  aria-label="Sites"
>
  <UiListItem
    variant="card"
    current
    slots={{ description, trailing }}
  ><a
      href="#acme"
    >Acme</a></UiListItem>
  <UiListItem
    variant="card"
    slots={{ description: listItemDescription }}
  ><a
      href="#globex"
    >Globex</a></UiListItem>
</UiList>
import { Component } from "@angular/core";
import { UiBadge, UiList, UiListItem } from "@threadlabs/looma";

@Component({
  selector: "example-view",
  standalone: true,
  imports: [UiList, UiListItem, UiBadge],
  template: `
    <ng-template
      ui-list
      [layout]="'grid'"
      [hnAttributes]="{ 'aria-label': 'Sites' }"
    >
      <li
        ui-list-item
        [variant]="'card'"
        [current]="true"
      >
        <a
          href="#acme"
        >Acme</a>
        <ng-container ngProjectAs="[slot='description']">
          <span>acme.example.com</span>
        </ng-container>
        <ng-container ngProjectAs="[slot='trailing']">
          <span
            ui-badge
          >Current</span>
        </ng-container>
      </li>
      <li
        ui-list-item
        [variant]="'card'"
      >
        <a
          href="#globex"
        >Globex</a>
        <ng-container ngProjectAs="[slot='description']">
          <span>globex.example.com</span>
        </ng-container>
      </li>
    </ng-template>
  `,
})
export default class Example {}

New items

Several items can have a quiet highlight without becoming selected or current. Keep the reason in their text.

  • Alex sent a new message New today
  • Sam shared a document New today
  • Earlier conversation Yesterday

Code

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

<ui-list
  aria-label="Messages"
>
  <ui-list-item
    highlighted
  >
    Alex sent a new message
    <span
      slot="description"
    >New today</span>
  </ui-list-item>
  <ui-list-item
    highlighted
  >
    Sam shared a document
    <span
      slot="description"
    >New today</span>
  </ui-list-item>
  <ui-list-item>
    Earlier conversation
    <span
      slot="description"
    >Yesterday</span>
  </ui-list-item>
</ui-list>
<script setup>
  import { UiList, UiListItem } from "@threadlabs/looma";
</script>

<template>
  <UiList
    aria-label="Messages"
  >
    <UiListItem
      :highlighted="true"
    >Alex sent a new message<template #description>
        <span>New today</span>
      </template></UiListItem>
    <UiListItem
      :highlighted="true"
    >Sam shared a document<template #description>
        <span>New today</span>
      </template></UiListItem>
    <UiListItem>Earlier conversation<template #description>
        <span>Yesterday</span>
      </template></UiListItem>
  </UiList>
</template>
import { UiList, UiListItem } from "@threadlabs/looma";

export default function Example() {
  return <UiList
    aria-label="Messages"
  >
    <UiListItem
      highlighted
      slots={{
        description: <span>New today</span>,
      }}
    >Alex sent a new message</UiListItem>
    <UiListItem
      highlighted
      slots={{
        description: <span>New today</span>,
      }}
    >Sam shared a document</UiListItem>
    <UiListItem
      slots={{
        description: <span>Yesterday</span>,
      }}
    >Earlier conversation</UiListItem>
  </UiList>;
}
/** @jsxImportSource preact */
import { UiList, UiListItem } from "@threadlabs/looma";

export default function Example() {
  return <UiList
    aria-label="Messages"
  >
    <UiListItem
      highlighted
      slots={{
        description: <span>New today</span>,
      }}
    >Alex sent a new message</UiListItem>
    <UiListItem
      highlighted
      slots={{
        description: <span>New today</span>,
      }}
    >Sam shared a document</UiListItem>
    <UiListItem
      slots={{
        description: <span>Yesterday</span>,
      }}
    >Earlier conversation</UiListItem>
  </UiList>;
}
<script>
  import { UiList, UiListItem } from "@threadlabs/looma";
</script>

{#snippet description()}
  <span>New today</span>
{/snippet}

{#snippet listItemDescription()}
  <span>New today</span>
{/snippet}

{#snippet listItemDescription2()}
  <span>Yesterday</span>
{/snippet}

<UiList
  aria-label="Messages"
>
  <UiListItem
    highlighted
    slots={{ description }}
  >Alex sent a new message</UiListItem>
  <UiListItem
    highlighted
    slots={{ description: listItemDescription }}
  >Sam shared a document</UiListItem>
  <UiListItem
    slots={{ description: listItemDescription2 }}
  >Earlier conversation</UiListItem>
</UiList>
import { Component } from "@angular/core";
import { UiList, UiListItem } from "@threadlabs/looma";

@Component({
  selector: "example-view",
  standalone: true,
  imports: [UiList, UiListItem],
  template: `
    <ng-template
      ui-list
      [hnAttributes]="{ 'aria-label': 'Messages' }"
    >
      <li
        ui-list-item
        [highlighted]="true"
      >Alex sent a new message<ng-container ngProjectAs="[slot='description']">
          <span>New today</span>
        </ng-container></li>
      <li
        ui-list-item
        [highlighted]="true"
      >Sam shared a document<ng-container ngProjectAs="[slot='description']">
          <span>New today</span>
        </ng-container></li>
      <li
        ui-list-item
      >Earlier conversation<ng-container ngProjectAs="[slot='description']">
          <span>Yesterday</span>
        </ng-container></li>
    </ng-template>
  `,
})
export default class Example {}

Wrapped descriptions

Complete explanatory text stays readable at narrow widths.

  • A person asked you to review the updated account recovery guide The request includes the recovery steps, device checks, and contact details. Read the full description before choosing what to do.

Code

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

<ui-list>
  <ui-list-item
    wrap
    highlighted
  >
    A person asked you to review the updated account recovery guide
    <span
      slot="description"
    >The request includes the recovery steps, device checks, and contact details. Read the full description before choosing what to do.</span>
    <ui-button
      slot="trailing"
      variant="outline"
      size="sm"
    >Review</ui-button>
  </ui-list-item>
</ui-list>
<script setup>
  import { UiButton, UiList, UiListItem } from "@threadlabs/looma";
</script>

<template>
  <UiList>
    <UiListItem
      :wrap="true"
      :highlighted="true"
    > A person asked you to review the updated account recovery guide <template #description>
        <span>The request includes the recovery steps, device checks, and contact details. Read the full description before choosing what to do.</span>
      </template> <template #trailing>
        <UiButton
          variant="outline"
          size="sm"
        >Review</UiButton>
      </template> </UiListItem>
  </UiList>
</template>
import { UiButton, UiList, UiListItem } from "@threadlabs/looma";

export default function Example() {
  return <UiList>
    <UiListItem
      wrap
      highlighted
      slots={{
        description: <span>The request includes the recovery steps, device checks, and contact details. Read the full description before choosing what to do.</span>,
        trailing: <UiButton
          variant="outline"
          size="sm"
        >Review</UiButton>,
      }}
    >{" A person asked you to review the updated account recovery guide "}{" "}{" "}</UiListItem>
  </UiList>;
}
/** @jsxImportSource preact */
import { UiButton, UiList, UiListItem } from "@threadlabs/looma";

export default function Example() {
  return <UiList>
    <UiListItem
      wrap
      highlighted
      slots={{
        description: <span>The request includes the recovery steps, device checks, and contact details. Read the full description before choosing what to do.</span>,
        trailing: <UiButton
          variant="outline"
          size="sm"
        >Review</UiButton>,
      }}
    >{" A person asked you to review the updated account recovery guide "}{" "}{" "}</UiListItem>
  </UiList>;
}
<script>
  import { UiButton, UiList, UiListItem } from "@threadlabs/looma";
</script>

{#snippet description()}
  <span>The request includes the recovery steps, device checks, and contact details. Read the full description before choosing what to do.</span>
{/snippet}

{#snippet trailing()}
  <UiButton
    variant="outline"
    size="sm"
  >Review</UiButton>
{/snippet}

<UiList>
  <UiListItem
    wrap
    highlighted
    slots={{ description, trailing }}
  > A person asked you to review the updated account recovery guide   </UiListItem>
</UiList>
import { Component } from "@angular/core";
import { UiButton, UiList, UiListItem } from "@threadlabs/looma";

@Component({
  selector: "example-view",
  standalone: true,
  imports: [UiList, UiListItem, UiButton],
  template: `
    <ng-template
      ui-list
    >
      <li
        ui-list-item
        [wrap]="true"
        [highlighted]="true"
      > A person asked you to review the updated account recovery guide <ng-container ngProjectAs="[slot='description']">
          <span>The request includes the recovery steps, device checks, and contact details. Read the full description before choosing what to do.</span>
        </ng-container> <ng-container ngProjectAs="[slot='trailing']">
          <ng-template
            ui-button
            [variant]="'outline'"
            [size]="'sm'"
          >Review</ng-template>
        </ng-container> </li>
    </ng-template>
  `,
})
export default class Example {}
Explore the example at a narrower width.

API

Package: @threadlabs/looma · Native root: ul

Attributes

NameDescriptionPropertyTypeDefaultAllowed values
asThe native list element. Use ol when item order matters; its native numbers are shown.askeywordulul, ol
densityHow much room each item takes. comfortable is a full row, easy to point at, for lists of things to open; compact sets items close together with no row padding, for a short list of facts inside other content, such as the features a plan includes.densitykeywordcomfortablecomfortable, compact
layoutHow the items are laid out. Rows stack the items one per line, for a list read top to bottom. Grid sets them in as many columns as fit, each at least --ui-list-min-item-width (14rem) wide, for card items such as pinned or featured entries.layoutkeywordrowsrows, grid
markersShow disc bullets for a ul of native li elements. An ol always shows native numbers.markerskeywordnonenone, disc

Framework props

NameTypeDefaultAllowed values
as"ul" | "ol"ulul, ol
density"comfortable" | "compact"comfortablecomfortable, compact
layout"rows" | "grid"rowsrows, grid
markers"none" | "disc"nonenone, disc

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-list-gap—var(--ui-space-1) | var(--ui-space-2) | var(--ui-space-3)
--ui-list-min-item-width—14rem

Shared tokens

TokenFallback
--ui-border-strong—
--ui-border-width—
--ui-font-normal—
--ui-radius-lg—
--ui-radius-md—
--ui-space-1—
--ui-space-2—
--ui-space-3—
--ui-space-4—
--ui-space-5—
--ui-surface-raised—
--ui-text-primary—

List Item API

One entry in a ui-list: leading, title, description, and trailing, with one-line title and description.

Package: @threadlabs/looma · Native root: li

Attributes

NameDescriptionPropertyTypeDefaultAllowed values
alignWhere the leading and trailing regions sit vertically. center aligns them with the whole row. title aligns them with the first title line, keeping an identifying icon beside its name when the title or description wraps. Taller controls retain their natural height.alignkeywordcentercenter, title
currentMarks the item as the current one, such as the site you are in: it takes the selected surface and aria-current="true", which assistive technology announces. Set it on one item at a time.currentbooleanfalse—
highlightedGives an item a quiet accent wash without changing its text or announcing selection. Several items may be highlighted, such as new messages. Describe the reason in the item's text; use current only for the current destination.highlightedbooleanfalse—
leading-interactiveMakes the leading region an independent control above the row link. Use it for a checkbox or button; keep noninteractive row icons in the title link when combining them with a leading control.leadingInteractivebooleanfalse—
variantHow the item is drawn. A row is flat and takes a hover surface when it is a link, for items read down a list. A card sits on a raised, bordered surface, for items set in a grid (a List with layout="grid"). Both have the same parts: leading, title, description, and trailing.variantkeywordrowrow, card
wrapLets the title and description wrap so explanatory text stays readable on narrow screens. Omit it for the default equal-height, one-line rows.wrapbooleanfalse—

Framework props

NameTypeDefaultAllowed values
align"center" | "title"centercenter, title
currentbooleanfalse—
highlightedbooleanfalse—
leadingInteractivebooleanfalse—
variant"row" | "card"rowrow, card
wrapbooleanfalse—

Events

No events.

Slots

NamePurpose
leadingNamed content for the leading region.
defaultDefault child content.
descriptionNamed content for the description region.
trailingNamed 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

TokenDeclared valuesFallback
--ui-list-item-gap—var(--ui-space-3)
--ui-list-item-icon-size—var(--ui-font-size-lg)
--ui-list-item-padding-block—var(--_ui-list-item-padding-block, var(--ui-space-2)) | var(--ui-space-3) | var(--ui-space-2)
--ui-list-item-padding-inline—var(--_ui-list-item-padding-inline, var(--ui-space-3)) | var(--ui-space-4) | var(--ui-space-3)

Shared tokens

TokenFallback
--ui-border-strong—
--ui-border-width—
--ui-control-size2.5rem
--ui-focus-ring—
--ui-focus-width—
--ui-font-medium—
--ui-font-size-lg—
--ui-font-size-md—
--ui-font-size-sm—
--ui-line-height-tight—
--ui-motion-ease—
--ui-motion-fast—
--ui-radius-lg—
--ui-selection-border—
--ui-selection-radius—
--ui-selection-surface—
--ui-selection-text—
--ui-space-0-52px
--ui-space-2—
--ui-space-3—
--ui-space-4—
--ui-surface-raised—
--ui-text-muted—
--ui-text-primary—