Search Shell
Responsive dialog structure for application search and command palettes. It owns visibility, dismissal, accessible dialog semantics, and the search/status/body/footer regions while applications retain query and result state.
Search commands
Open the interactive surface when needed; it starts closed in the component directory.
Code
<link
rel="component"
href="@threadlabs/looma/components/ui-button/ui-button.html"
>
<link
rel="component"
href="@threadlabs/looma/components/ui-search-shell/ui-search-shell.html"
>
<link
rel="component"
href="@threadlabs/looma/components/ui-input/ui-input.html"
>
<link
rel="component"
href="@threadlabs/looma/components/ui-search-result-row/ui-search-result-row.html"
>
<link
rel="component"
href="@threadlabs/looma/components/ui-icon/ui-icon.html"
>
<ui-button
data-demo-trigger
>Search commands</ui-button>
<ui-search-shell
data-demo-surface
dismissible
label="Search commands"
>
<ui-input
slot="search"
type="search"
aria-label="Search commands"
placeholder="Find a command"
></ui-input>
<ui-search-result-row
slot="body"
>
<ui-icon
slot="leading"
name="file-text"
></ui-icon>
<span
slot="title"
>Team notes</span>
</ui-search-result-row>
</ui-search-shell><script setup>
import { UiButton, UiIcon, UiInput, UiSearchResultRow, UiSearchShell } from "@threadlabs/looma";
</script>
<template>
<UiButton
data-demo-trigger=""
>Search commands</UiButton>
<UiSearchShell
data-demo-surface=""
:dismissible="true"
label="Search commands"
>
<template #search>
<UiInput
type="search"
aria-label="Search commands"
placeholder="Find a command"
/>
</template>
<template #body>
<UiSearchResultRow>
<template #leading>
<UiIcon
name="file-text"
/>
</template>
<template #title>
<span>Team notes</span>
</template>
</UiSearchResultRow>
</template>
</UiSearchShell>
</template>import { UiButton, UiIcon, UiInput, UiSearchResultRow, UiSearchShell } from "@threadlabs/looma";
export default function Example() {
return <>
<UiButton
data-demo-trigger=""
>Search commands</UiButton>
<UiSearchShell
data-demo-surface=""
dismissible
label="Search commands"
slots={{
search: <UiInput
type="search"
aria-label="Search commands"
placeholder="Find a command"
/>,
body: <UiSearchResultRow
slots={{
leading: <UiIcon
name="file-text"
/>,
title: <span>Team notes</span>,
}}
/>,
}}
/>
</>;
}/** @jsxImportSource preact */
import { UiButton, UiIcon, UiInput, UiSearchResultRow, UiSearchShell } from "@threadlabs/looma";
export default function Example() {
return <>
<UiButton
data-demo-trigger=""
>Search commands</UiButton>
<UiSearchShell
data-demo-surface=""
dismissible
label="Search commands"
slots={{
search: <UiInput
type="search"
aria-label="Search commands"
placeholder="Find a command"
/>,
body: <UiSearchResultRow
slots={{
leading: <UiIcon
name="file-text"
/>,
title: <span>Team notes</span>,
}}
/>,
}}
/>
</>;
}<script>
import { UiButton, UiIcon, UiInput, UiSearchResultRow, UiSearchShell } from "@threadlabs/looma";
</script>
{#snippet search()}
<UiInput
type="search"
aria-label="Search commands"
placeholder="Find a command"
/>
{/snippet}
{#snippet body()}
<UiSearchResultRow
slots={{ leading, title }}
/>
{/snippet}
{#snippet leading()}
<UiIcon
name="file-text"
/>
{/snippet}
{#snippet title()}
<span>Team notes</span>
{/snippet}
<UiButton
data-demo-trigger=""
>Search commands</UiButton>
<UiSearchShell
data-demo-surface=""
dismissible
label="Search commands"
slots={{ search, body }}
/>import { Component } from "@angular/core";
import { UiButton, UiIcon, UiInput, UiSearchResultRow, UiSearchShell } from "@threadlabs/looma";
@Component({
selector: "example-view",
standalone: true,
imports: [UiButton, UiSearchShell, UiInput, UiSearchResultRow, UiIcon],
template: `
<ng-template
ui-button
[hnAttributes]="{ 'data-demo-trigger': '' }"
>Search commands</ng-template>
<div
ui-search-shell
data-demo-surface=""
[dismissible]="true"
[label]="'Search commands'"
>
<ng-container ngProjectAs="[slot='search']">
<input
ui-input
[type]="'search'"
aria-label="Search commands"
placeholder="Find a command"
>
</ng-container>
<ng-container ngProjectAs="[slot='body']">
<button
ui-search-result-row
>
<ng-container ngProjectAs="[slot='leading']">
<span
ui-icon
name="file-text"
></span>
</ng-container>
<ng-container ngProjectAs="[slot='title']">
<span>Team notes</span>
</ng-container>
</button>
</ng-container>
</div>
`,
})
export default class Example {}open
Open reveals the labeled shell while the optional status and footer regions remain absent.
Code
<link
rel="component"
href="@threadlabs/looma/components/ui-search-shell/ui-search-shell.html"
>
<link
rel="component"
href="@threadlabs/looma/components/ui-input/ui-input.html"
>
<ui-search-shell
open
label="Search commands"
>
<ui-input
slot="search"
type="search"
aria-label="Search commands"
placeholder="Search commands"
> </ui-input>
<div
slot="body"
>Start typing to filter commands.</div>
</ui-search-shell><script setup>
import { UiInput, UiSearchShell } from "@threadlabs/looma";
</script>
<template>
<UiSearchShell
:open="true"
label="Search commands"
>
<template #search>
<UiInput
type="search"
aria-label="Search commands"
placeholder="Search commands"
> </UiInput>
</template>
<template #body>
<div>Start typing to filter commands.</div>
</template>
</UiSearchShell>
</template>import { UiInput, UiSearchShell } from "@threadlabs/looma";
export default function Example() {
return <UiSearchShell
open
label="Search commands"
slots={{
search: <UiInput
type="search"
aria-label="Search commands"
placeholder="Search commands"
> </UiInput>,
body: <div>Start typing to filter commands.</div>,
}}
>{" "}{" "}{" "}</UiSearchShell>;
}/** @jsxImportSource preact */
import { UiInput, UiSearchShell } from "@threadlabs/looma";
export default function Example() {
return <UiSearchShell
open
label="Search commands"
slots={{
search: <UiInput
type="search"
aria-label="Search commands"
placeholder="Search commands"
> </UiInput>,
body: <div>Start typing to filter commands.</div>,
}}
>{" "}{" "}{" "}</UiSearchShell>;
}<script>
import { UiInput, UiSearchShell } from "@threadlabs/looma";
</script>
{#snippet search()}
<UiInput
type="search"
aria-label="Search commands"
placeholder="Search commands"
> </UiInput>
{/snippet}
{#snippet body()}
<div>Start typing to filter commands.</div>
{/snippet}
<UiSearchShell
open
label="Search commands"
slots={{ search, body }}
> </UiSearchShell>import { Component } from "@angular/core";
import { UiInput, UiSearchShell } from "@threadlabs/looma";
@Component({
selector: "example-view",
standalone: true,
imports: [UiSearchShell, UiInput],
template: `
<div
ui-search-shell
[open]="true"
[label]="'Search commands'"
>
<ng-container ngProjectAs="[slot='search']">
<input
ui-input
[type]="'search'"
aria-label="Search commands"
placeholder="Search commands"
>
</ng-container>
<ng-container ngProjectAs="[slot='body']">
<div>Start typing to filter commands.</div>
</ng-container>
</div>
`,
})
export default class Example {}label, dismissible, status, and footer
Label names the dialog, dismissible enables backdrop dismissal, and the status, body, and footer slots host application-owned search state. Escape always closes it.
Code
<script
type="module"
>
import "@threadlabs/looma";
const input = document.querySelector("#docs-search-input");
const status = document.querySelector("#docs-search-status");
const empty = document.querySelector("#docs-search-empty");
const results = [
document.querySelector("#docs-result-design-tokens"),
document.querySelector("#docs-result-token-overrides"),
document.querySelector("#docs-result-button-variants")
];
function updateResults() {
const query = input.value.trim().toLowerCase();
let visible = 0;
for (const result of results) {
result.hidden = !result.textContent.toLowerCase().includes(query);
if (!result.hidden) visible += 1;
}
status.textContent = visible + " result" + (visible === 1 ? "" : "s");
empty.hidden = visible !== 0;
}
input.addEventListener("input", updateResults);
</script>
<ui-search-shell
id="docs-search-shell"
open
dismissible
label="Search documentation"
>
<ui-input
id="docs-search-input"
slot="search"
type="search"
aria-label="Search documentation"
placeholder="Search documentation"
></ui-input>
<div
id="docs-search-status"
slot="status"
aria-live="polite"
>3 results</div>
<div
id="docs-search-results"
slot="body"
>
<ui-search-result-row
id="docs-result-design-tokens"
>
<strong
slot="title"
>Design tokens</strong>
<span
slot="excerpt"
>Color, type, spacing, and motion.</span>
</ui-search-result-row>
<ui-search-result-row
id="docs-result-token-overrides"
>
<strong
slot="title"
>Token overrides</strong>
<span
slot="excerpt"
>Customize shared and component tokens.</span>
</ui-search-result-row>
<ui-search-result-row
id="docs-result-button-variants"
>
<strong
slot="title"
>Button variants</strong>
<span
slot="excerpt"
>Choose solid, outline, ghost, or danger.</span>
</ui-search-result-row>
<p
id="docs-search-empty"
hidden
>No matching documentation.</p>
</div>
<form
slot="footer"
method="dialog"
>
<ui-button
type="submit"
size="sm"
>Close</ui-button>
</form>
</ui-search-shell>
<script setup lang="ts">
import { computed, ref } from "vue";
import { UiButton as Button, UiInput as Input, UiSearchResultRow as SearchResultRow, UiSearchShell as SearchShell } from "@threadlabs/looma";
const query = ref("");
const results = [
["Design tokens", "Color, type, spacing, and motion."],
["Token overrides", "Customize shared and component tokens."],
["Button variants", "Choose solid, outline, ghost, or danger."]
];
const filtered = computed(() => results.filter((result) =>
result.join(" ").toLowerCase().includes(query.value.toLowerCase())
));
</script>
<template>
<SearchShell open dismissible label="Search documentation">
<template #search>
<Input type="search" aria-label="Search documentation"
:value="query" @input="query = $event.target.value" />
</template>
<template #status><div aria-live="polite">{{ filtered.length }} results</div></template>
<template #body>
<SearchResultRow v-for="result in filtered" :key="result[0]">
<template #title><strong>{{ result[0] }}</strong></template>
<template #excerpt><span>{{ result[1] }}</span></template>
</SearchResultRow>
<p v-if="filtered.length === 0">No matching documentation.</p>
</template>
<template #footer><form method="dialog"><Button type="submit" size="sm">Close</Button></form></template>
</SearchShell>
</template>
import { UiButton, UiInput, UiSearchResultRow, UiSearchShell } from "@threadlabs/looma";
export default function Example() {
return <UiSearchShell
id="docs-search-shell"
data-example-id="docs-search-shell"
open
dismissible
label="Search documentation"
slots={{
search: <UiInput
id="docs-search-input"
data-example-id="docs-search-input"
type="search"
aria-label="Search documentation"
placeholder="Search documentation"
>{" "}</UiInput>,
status: <div
id="docs-search-status"
data-example-id="docs-search-status"
aria-live="polite"
>3 results</div>,
body: <div
id="docs-search-results"
data-example-id="docs-search-results"
>
<UiSearchResultRow
id="docs-result-design-tokens"
data-example-id="docs-result-design-tokens"
slots={{
title: <strong>Design tokens</strong>,
excerpt: <span>Color, type, spacing, and motion.</span>,
}}
>{" "}{" "}{" "}</UiSearchResultRow>
<UiSearchResultRow
id="docs-result-token-overrides"
data-example-id="docs-result-token-overrides"
slots={{
title: <strong>Token overrides</strong>,
excerpt: <span>Customize shared and component tokens.</span>,
}}
>{" "}{" "}{" "}</UiSearchResultRow>
<UiSearchResultRow
id="docs-result-button-variants"
data-example-id="docs-result-button-variants"
slots={{
title: <strong>Button variants</strong>,
excerpt: <span>Choose solid, outline, ghost, or danger.</span>,
}}
>{" "}{" "}{" "}</UiSearchResultRow>
<p
id="docs-search-empty"
data-example-id="docs-search-empty"
hidden
>No matching documentation.</p>
</div>,
footer: <form
method="dialog"
>
<UiButton
type="submit"
size="sm"
>Close</UiButton>
</form>,
}}
>{" "}{" "}{" "}{" "}{" "}</UiSearchShell>;
}/** @jsxImportSource preact */
import { UiButton, UiInput, UiSearchResultRow, UiSearchShell } from "@threadlabs/looma";
export default function Example() {
return <UiSearchShell
id="docs-search-shell"
data-example-id="docs-search-shell"
open
dismissible
label="Search documentation"
slots={{
search: <UiInput
id="docs-search-input"
data-example-id="docs-search-input"
type="search"
aria-label="Search documentation"
placeholder="Search documentation"
>{" "}</UiInput>,
status: <div
id="docs-search-status"
data-example-id="docs-search-status"
aria-live="polite"
>3 results</div>,
body: <div
id="docs-search-results"
data-example-id="docs-search-results"
>
<UiSearchResultRow
id="docs-result-design-tokens"
data-example-id="docs-result-design-tokens"
slots={{
title: <strong>Design tokens</strong>,
excerpt: <span>Color, type, spacing, and motion.</span>,
}}
>{" "}{" "}{" "}</UiSearchResultRow>
<UiSearchResultRow
id="docs-result-token-overrides"
data-example-id="docs-result-token-overrides"
slots={{
title: <strong>Token overrides</strong>,
excerpt: <span>Customize shared and component tokens.</span>,
}}
>{" "}{" "}{" "}</UiSearchResultRow>
<UiSearchResultRow
id="docs-result-button-variants"
data-example-id="docs-result-button-variants"
slots={{
title: <strong>Button variants</strong>,
excerpt: <span>Choose solid, outline, ghost, or danger.</span>,
}}
>{" "}{" "}{" "}</UiSearchResultRow>
<p
id="docs-search-empty"
data-example-id="docs-search-empty"
hidden
>No matching documentation.</p>
</div>,
footer: <form
method="dialog"
>
<UiButton
type="submit"
size="sm"
>Close</UiButton>
</form>,
}}
>{" "}{" "}{" "}{" "}{" "}</UiSearchShell>;
}<script lang="ts">
import "@threadlabs/looma";
let query = "";
const results = [
["Design tokens", "Color, type, spacing, and motion."],
["Token overrides", "Customize shared and component tokens."],
["Button variants", "Choose solid, outline, ghost, or danger."]
];
$: filtered = results.filter((result) =>
result.join(" ").toLowerCase().includes(query.toLowerCase())
);
</script>
<ui-search-shell open dismissible label="Search documentation">
<ui-input slot="search" type="search" aria-label="Search documentation"
value={query} oninput={(event) => query = event.currentTarget.value}></ui-input>
<div slot="status" aria-live="polite">{filtered.length} results</div>
<div slot="body">
{#each filtered as result (result[0])}
<ui-search-result-row>
<strong slot="title">{result[0]}</strong><span slot="excerpt">{result[1]}</span>
</ui-search-result-row>
{/each}
{#if filtered.length === 0}<p>No matching documentation.</p>{/if}
</div>
<form slot="footer" method="dialog"><ui-button type="submit" size="sm">Close</ui-button></form>
</ui-search-shell>
import { Component } from "@angular/core";
import { UiButton, UiInput, UiSearchResultRow, UiSearchShell } from "@threadlabs/looma";
@Component({
selector: "example-view",
standalone: true,
imports: [UiSearchShell, UiInput, UiSearchResultRow, UiButton],
template: `
<div
ui-search-shell
id="docs-search-shell"
data-example-id="docs-search-shell"
[open]="true"
[dismissible]="true"
[label]="'Search documentation'"
>
<ng-container ngProjectAs="[slot='search']">
<input
ui-input
id="docs-search-input"
data-example-id="docs-search-input"
[type]="'search'"
aria-label="Search documentation"
placeholder="Search documentation"
>
</ng-container>
<ng-container ngProjectAs="[slot='status']">
<div
id="docs-search-status"
data-example-id="docs-search-status"
aria-live="polite"
>3 results</div>
</ng-container>
<ng-container ngProjectAs="[slot='body']">
<div
id="docs-search-results"
data-example-id="docs-search-results"
>
<button
ui-search-result-row
id="docs-result-design-tokens"
data-example-id="docs-result-design-tokens"
>
<ng-container ngProjectAs="[slot='title']">
<strong>Design tokens</strong>
</ng-container>
<ng-container ngProjectAs="[slot='excerpt']">
<span>Color, type, spacing, and motion.</span>
</ng-container>
</button>
<button
ui-search-result-row
id="docs-result-token-overrides"
data-example-id="docs-result-token-overrides"
>
<ng-container ngProjectAs="[slot='title']">
<strong>Token overrides</strong>
</ng-container>
<ng-container ngProjectAs="[slot='excerpt']">
<span>Customize shared and component tokens.</span>
</ng-container>
</button>
<button
ui-search-result-row
id="docs-result-button-variants"
data-example-id="docs-result-button-variants"
>
<ng-container ngProjectAs="[slot='title']">
<strong>Button variants</strong>
</ng-container>
<ng-container ngProjectAs="[slot='excerpt']">
<span>Choose solid, outline, ghost, or danger.</span>
</ng-container>
</button>
<p
id="docs-search-empty"
data-example-id="docs-search-empty"
hidden
>No matching documentation.</p>
</div>
</ng-container>
<ng-container ngProjectAs="[slot='footer']">
<form
method="dialog"
>
<ng-template
ui-button
[type]="'submit'"
[size]="'sm'"
>Close</ng-template>
</form>
</ng-container>
</div>
`,
})
export default class Example {}Default
With selected and disabled omitted, Search Result Row renders its normal state.
Code
<link
rel="component"
href="@threadlabs/looma/components/ui-search-result-row/ui-search-result-row.html"
>
<ui-search-result-row> <span
slot="leading"
>○</span> <span
slot="title"
>Result title</span> <span
slot="meta"
>Metadata</span> </ui-search-result-row><script setup>
import { UiSearchResultRow } from "@threadlabs/looma";
</script>
<template>
<UiSearchResultRow>
<template #leading>
<span>○</span>
</template>
<template #title>
<span>Result title</span>
</template>
<template #meta>
<span>Metadata</span>
</template>
</UiSearchResultRow>
</template>import { UiSearchResultRow } from "@threadlabs/looma";
export default function Example() {
return <UiSearchResultRow
slots={{
leading: <span>○</span>,
title: <span>Result title</span>,
meta: <span>Metadata</span>,
}}
>{" "}{" "}{" "}{" "}</UiSearchResultRow>;
}/** @jsxImportSource preact */
import { UiSearchResultRow } from "@threadlabs/looma";
export default function Example() {
return <UiSearchResultRow
slots={{
leading: <span>○</span>,
title: <span>Result title</span>,
meta: <span>Metadata</span>,
}}
>{" "}{" "}{" "}{" "}</UiSearchResultRow>;
}<script>
import { UiSearchResultRow } from "@threadlabs/looma";
</script>
{#snippet leading()}
<span>○</span>
{/snippet}
{#snippet title()}
<span>Result title</span>
{/snippet}
{#snippet meta()}
<span>Metadata</span>
{/snippet}
<UiSearchResultRow
slots={{ leading, title, meta }}
> </UiSearchResultRow>import { Component } from "@angular/core";
import { UiSearchResultRow } from "@threadlabs/looma";
@Component({
selector: "example-view",
standalone: true,
imports: [UiSearchResultRow],
template: `
<button
ui-search-result-row
>
<ng-container ngProjectAs="[slot='leading']">
<span>○</span>
</ng-container>
<ng-container ngProjectAs="[slot='title']">
<span>Result title</span>
</ng-container>
<ng-container ngProjectAs="[slot='meta']">
<span>Metadata</span>
</ng-container>
</button>
`,
})
export default class Example {}selected and disabled
Selected marks the current result, visibly and with aria-current; disabled keeps an unavailable result visible but inert.
Code
<link
rel="component"
href="@threadlabs/looma/components/ui-search-result-row/ui-search-result-row.html"
>
<div>
<ui-search-result-row
selected
> <span
slot="title"
>Selected result</span> <span
slot="meta"
>Available</span> </ui-search-result-row>
<ui-search-result-row
disabled
> <span
slot="title"
>Disabled result</span> <span
slot="meta"
>Unavailable</span> </ui-search-result-row>
</div><script setup>
import { UiSearchResultRow } from "@threadlabs/looma";
</script>
<template>
<div>
<UiSearchResultRow
:selected="true"
>
<template #title>
<span>Selected result</span>
</template>
<template #meta>
<span>Available</span>
</template>
</UiSearchResultRow>
<UiSearchResultRow
:disabled="true"
>
<template #title>
<span>Disabled result</span>
</template>
<template #meta>
<span>Unavailable</span>
</template>
</UiSearchResultRow>
</div>
</template>import { UiSearchResultRow } from "@threadlabs/looma";
export default function Example() {
return <div>
<UiSearchResultRow
selected
slots={{
title: <span>Selected result</span>,
meta: <span>Available</span>,
}}
>{" "}{" "}{" "}</UiSearchResultRow>
<UiSearchResultRow
disabled
slots={{
title: <span>Disabled result</span>,
meta: <span>Unavailable</span>,
}}
>{" "}{" "}{" "}</UiSearchResultRow>
</div>;
}/** @jsxImportSource preact */
import { UiSearchResultRow } from "@threadlabs/looma";
export default function Example() {
return <div>
<UiSearchResultRow
selected
slots={{
title: <span>Selected result</span>,
meta: <span>Available</span>,
}}
>{" "}{" "}{" "}</UiSearchResultRow>
<UiSearchResultRow
disabled
slots={{
title: <span>Disabled result</span>,
meta: <span>Unavailable</span>,
}}
>{" "}{" "}{" "}</UiSearchResultRow>
</div>;
}<script>
import { UiSearchResultRow } from "@threadlabs/looma";
</script>
{#snippet title()}
<span>Selected result</span>
{/snippet}
{#snippet meta()}
<span>Available</span>
{/snippet}
{#snippet searchResultRowTitle()}
<span>Disabled result</span>
{/snippet}
{#snippet searchResultRowMeta()}
<span>Unavailable</span>
{/snippet}
<div>
<UiSearchResultRow
selected
slots={{ title, meta }}
> </UiSearchResultRow>
<UiSearchResultRow
disabled
slots={{ title: searchResultRowTitle, meta: searchResultRowMeta }}
> </UiSearchResultRow>
</div>import { Component } from "@angular/core";
import { UiSearchResultRow } from "@threadlabs/looma";
@Component({
selector: "example-view",
standalone: true,
imports: [UiSearchResultRow],
template: `
<div>
<button
ui-search-result-row
[selected]="true"
>
<ng-container ngProjectAs="[slot='title']">
<span>Selected result</span>
</ng-container>
<ng-container ngProjectAs="[slot='meta']">
<span>Available</span>
</ng-container>
</button>
<button
ui-search-result-row
[disabled]="true"
>
<ng-container ngProjectAs="[slot='title']">
<span>Disabled result</span>
</ng-container>
<ng-container ngProjectAs="[slot='meta']">
<span>Unavailable</span>
</ng-container>
</button>
</div>
`,
})
export default class Example {}Compact results
Three compact title-and-location rows fit in a small scroll area; the remaining results scroll.
Code
<link
rel="component"
href="@threadlabs/looma/components/ui-scroll-area/ui-scroll-area.html"
>
<link
rel="component"
href="@threadlabs/looma/components/ui-search-result-row/ui-search-result-row.html"
>
<link
rel="component"
href="@threadlabs/looma/components/ui-icon/ui-icon.html"
>
<link
rel="component"
href="@threadlabs/looma/components/ui-text/ui-text.html"
>
<ui-scroll-area
size="sm"
>
<ui-search-result-row
density="compact"
>
<ui-icon
slot="leading"
name="file-text"
></ui-icon>
<ui-text
slot="title"
as="strong"
size="sm"
weight="medium"
>Guide</ui-text>
<ui-text
slot="meta"
as="span"
size="xs"
tone="secondary"
>Team / Guides</ui-text>
</ui-search-result-row>
<ui-search-result-row
density="compact"
>
<ui-icon
slot="leading"
name="file-text"
></ui-icon>
<ui-text
slot="title"
as="strong"
size="sm"
weight="medium"
>Handbook</ui-text>
<ui-text
slot="meta"
as="span"
size="xs"
tone="secondary"
>Team / References</ui-text>
</ui-search-result-row>
<ui-search-result-row
density="compact"
>
<ui-icon
slot="leading"
name="file-text"
></ui-icon>
<ui-text
slot="title"
as="strong"
size="sm"
weight="medium"
>Glossary</ui-text>
<ui-text
slot="meta"
as="span"
size="xs"
tone="secondary"
>Team / References</ui-text>
</ui-search-result-row>
<ui-search-result-row
density="compact"
>
<ui-icon
slot="leading"
name="file-text"
></ui-icon>
<ui-text
slot="title"
as="strong"
size="sm"
weight="medium"
>Notes</ui-text>
<ui-text
slot="meta"
as="span"
size="xs"
tone="secondary"
>Personal / Notes</ui-text>
</ui-search-result-row>
</ui-scroll-area><script setup>
import { UiIcon, UiScrollArea, UiSearchResultRow, UiText } from "@threadlabs/looma";
</script>
<template>
<UiScrollArea
size="sm"
>
<UiSearchResultRow
density="compact"
>
<template #leading>
<UiIcon
name="file-text"
/>
</template>
<template #title>
<UiText
as="strong"
size="sm"
weight="medium"
>Guide</UiText>
</template>
<template #meta>
<UiText
as="span"
size="xs"
tone="secondary"
>Team / Guides</UiText>
</template>
</UiSearchResultRow>
<UiSearchResultRow
density="compact"
>
<template #leading>
<UiIcon
name="file-text"
/>
</template>
<template #title>
<UiText
as="strong"
size="sm"
weight="medium"
>Handbook</UiText>
</template>
<template #meta>
<UiText
as="span"
size="xs"
tone="secondary"
>Team / References</UiText>
</template>
</UiSearchResultRow>
<UiSearchResultRow
density="compact"
>
<template #leading>
<UiIcon
name="file-text"
/>
</template>
<template #title>
<UiText
as="strong"
size="sm"
weight="medium"
>Glossary</UiText>
</template>
<template #meta>
<UiText
as="span"
size="xs"
tone="secondary"
>Team / References</UiText>
</template>
</UiSearchResultRow>
<UiSearchResultRow
density="compact"
>
<template #leading>
<UiIcon
name="file-text"
/>
</template>
<template #title>
<UiText
as="strong"
size="sm"
weight="medium"
>Notes</UiText>
</template>
<template #meta>
<UiText
as="span"
size="xs"
tone="secondary"
>Personal / Notes</UiText>
</template>
</UiSearchResultRow>
</UiScrollArea>
</template>import { UiIcon, UiScrollArea, UiSearchResultRow, UiText } from "@threadlabs/looma";
export default function Example() {
return <UiScrollArea
size="sm"
>
<UiSearchResultRow
density="compact"
slots={{
leading: <UiIcon
name="file-text"
/>,
title: <UiText
as="strong"
size="sm"
weight="medium"
>Guide</UiText>,
meta: <UiText
as="span"
size="xs"
tone="secondary"
>Team / Guides</UiText>,
}}
>{" "}{" "}{" "}{" "}</UiSearchResultRow>
<UiSearchResultRow
density="compact"
slots={{
leading: <UiIcon
name="file-text"
/>,
title: <UiText
as="strong"
size="sm"
weight="medium"
>Handbook</UiText>,
meta: <UiText
as="span"
size="xs"
tone="secondary"
>Team / References</UiText>,
}}
>{" "}{" "}{" "}{" "}</UiSearchResultRow>
<UiSearchResultRow
density="compact"
slots={{
leading: <UiIcon
name="file-text"
/>,
title: <UiText
as="strong"
size="sm"
weight="medium"
>Glossary</UiText>,
meta: <UiText
as="span"
size="xs"
tone="secondary"
>Team / References</UiText>,
}}
>{" "}{" "}{" "}{" "}</UiSearchResultRow>
<UiSearchResultRow
density="compact"
slots={{
leading: <UiIcon
name="file-text"
/>,
title: <UiText
as="strong"
size="sm"
weight="medium"
>Notes</UiText>,
meta: <UiText
as="span"
size="xs"
tone="secondary"
>Personal / Notes</UiText>,
}}
>{" "}{" "}{" "}{" "}</UiSearchResultRow>
</UiScrollArea>;
}/** @jsxImportSource preact */
import { UiIcon, UiScrollArea, UiSearchResultRow, UiText } from "@threadlabs/looma";
export default function Example() {
return <UiScrollArea
size="sm"
>
<UiSearchResultRow
density="compact"
slots={{
leading: <UiIcon
name="file-text"
/>,
title: <UiText
as="strong"
size="sm"
weight="medium"
>Guide</UiText>,
meta: <UiText
as="span"
size="xs"
tone="secondary"
>Team / Guides</UiText>,
}}
>{" "}{" "}{" "}{" "}</UiSearchResultRow>
<UiSearchResultRow
density="compact"
slots={{
leading: <UiIcon
name="file-text"
/>,
title: <UiText
as="strong"
size="sm"
weight="medium"
>Handbook</UiText>,
meta: <UiText
as="span"
size="xs"
tone="secondary"
>Team / References</UiText>,
}}
>{" "}{" "}{" "}{" "}</UiSearchResultRow>
<UiSearchResultRow
density="compact"
slots={{
leading: <UiIcon
name="file-text"
/>,
title: <UiText
as="strong"
size="sm"
weight="medium"
>Glossary</UiText>,
meta: <UiText
as="span"
size="xs"
tone="secondary"
>Team / References</UiText>,
}}
>{" "}{" "}{" "}{" "}</UiSearchResultRow>
<UiSearchResultRow
density="compact"
slots={{
leading: <UiIcon
name="file-text"
/>,
title: <UiText
as="strong"
size="sm"
weight="medium"
>Notes</UiText>,
meta: <UiText
as="span"
size="xs"
tone="secondary"
>Personal / Notes</UiText>,
}}
>{" "}{" "}{" "}{" "}</UiSearchResultRow>
</UiScrollArea>;
}<script>
import { UiIcon, UiScrollArea, UiSearchResultRow, UiText } from "@threadlabs/looma";
</script>
{#snippet leading()}
<UiIcon
name="file-text"
/>
{/snippet}
{#snippet title()}
<UiText
as="strong"
size="sm"
weight="medium"
>Guide</UiText>
{/snippet}
{#snippet meta()}
<UiText
as="span"
size="xs"
tone="secondary"
>Team / Guides</UiText>
{/snippet}
{#snippet searchResultRowLeading()}
<UiIcon
name="file-text"
/>
{/snippet}
{#snippet searchResultRowTitle()}
<UiText
as="strong"
size="sm"
weight="medium"
>Handbook</UiText>
{/snippet}
{#snippet searchResultRowMeta()}
<UiText
as="span"
size="xs"
tone="secondary"
>Team / References</UiText>
{/snippet}
{#snippet searchResultRowLeading2()}
<UiIcon
name="file-text"
/>
{/snippet}
{#snippet searchResultRowTitle2()}
<UiText
as="strong"
size="sm"
weight="medium"
>Glossary</UiText>
{/snippet}
{#snippet searchResultRowMeta2()}
<UiText
as="span"
size="xs"
tone="secondary"
>Team / References</UiText>
{/snippet}
{#snippet searchResultRowLeading3()}
<UiIcon
name="file-text"
/>
{/snippet}
{#snippet searchResultRowTitle3()}
<UiText
as="strong"
size="sm"
weight="medium"
>Notes</UiText>
{/snippet}
{#snippet searchResultRowMeta3()}
<UiText
as="span"
size="xs"
tone="secondary"
>Personal / Notes</UiText>
{/snippet}
<UiScrollArea
size="sm"
>
<UiSearchResultRow
density="compact"
slots={{ leading, title, meta }}
> </UiSearchResultRow>
<UiSearchResultRow
density="compact"
slots={{ leading: searchResultRowLeading, title: searchResultRowTitle, meta: searchResultRowMeta }}
> </UiSearchResultRow>
<UiSearchResultRow
density="compact"
slots={{ leading: searchResultRowLeading2, title: searchResultRowTitle2, meta: searchResultRowMeta2 }}
> </UiSearchResultRow>
<UiSearchResultRow
density="compact"
slots={{ leading: searchResultRowLeading3, title: searchResultRowTitle3, meta: searchResultRowMeta3 }}
> </UiSearchResultRow>
</UiScrollArea>import { Component } from "@angular/core";
import { UiIcon, UiScrollArea, UiSearchResultRow, UiText } from "@threadlabs/looma";
@Component({
selector: "example-view",
standalone: true,
imports: [UiScrollArea, UiSearchResultRow, UiIcon, UiText],
template: `
<div
ui-scroll-area
[size]="'sm'"
>
<button
ui-search-result-row
[density]="'compact'"
>
<ng-container ngProjectAs="[slot='leading']">
<span
ui-icon
name="file-text"
></span>
</ng-container>
<ng-container ngProjectAs="[slot='title']">
<ng-template
ui-text
[as]="'strong'"
[size]="'sm'"
[weight]="'medium'"
>Guide</ng-template>
</ng-container>
<ng-container ngProjectAs="[slot='meta']">
<ng-template
ui-text
[as]="'span'"
[size]="'xs'"
[tone]="'secondary'"
>Team / Guides</ng-template>
</ng-container>
</button>
<button
ui-search-result-row
[density]="'compact'"
>
<ng-container ngProjectAs="[slot='leading']">
<span
ui-icon
name="file-text"
></span>
</ng-container>
<ng-container ngProjectAs="[slot='title']">
<ng-template
ui-text
[as]="'strong'"
[size]="'sm'"
[weight]="'medium'"
>Handbook</ng-template>
</ng-container>
<ng-container ngProjectAs="[slot='meta']">
<ng-template
ui-text
[as]="'span'"
[size]="'xs'"
[tone]="'secondary'"
>Team / References</ng-template>
</ng-container>
</button>
<button
ui-search-result-row
[density]="'compact'"
>
<ng-container ngProjectAs="[slot='leading']">
<span
ui-icon
name="file-text"
></span>
</ng-container>
<ng-container ngProjectAs="[slot='title']">
<ng-template
ui-text
[as]="'strong'"
[size]="'sm'"
[weight]="'medium'"
>Glossary</ng-template>
</ng-container>
<ng-container ngProjectAs="[slot='meta']">
<ng-template
ui-text
[as]="'span'"
[size]="'xs'"
[tone]="'secondary'"
>Team / References</ng-template>
</ng-container>
</button>
<button
ui-search-result-row
[density]="'compact'"
>
<ng-container ngProjectAs="[slot='leading']">
<span
ui-icon
name="file-text"
></span>
</ng-container>
<ng-container ngProjectAs="[slot='title']">
<ng-template
ui-text
[as]="'strong'"
[size]="'sm'"
[weight]="'medium'"
>Notes</ng-template>
</ng-container>
<ng-container ngProjectAs="[slot='meta']">
<ng-template
ui-text
[as]="'span'"
[size]="'xs'"
[tone]="'secondary'"
>Personal / Notes</ng-template>
</ng-container>
</button>
</div>
`,
})
export default class Example {}API
Package: @threadlabs/looma · Native root: div
Attributes
| Name | Description | Property | Type | Default | Allowed values |
|---|---|---|---|---|---|
dismissible | Allows backdrop activation to close the search surface. Escape always closes it. | dismissible | boolean | false | — |
label | Provides the native dialog's accessible name. | label | string | Search | — |
modal | Makes the rest of the page inert while the search surface is open. | modal | boolean | false | — |
open | Shows the search surface. | open | boolean | false | — |
Framework props
| Name | Type | Default | Allowed values |
|---|---|---|---|
dismissible | boolean | false | — |
label | string | Search | — |
modal | boolean | false | — |
open | boolean | false | — |
Events
| Name | Detail | Description |
|---|---|---|
close | object
| Emitted when the component transitions to a closed state. |
Slots
| Name | Purpose |
|---|---|
search | Named content for the search region. |
status | Named content for the status region. |
body | Named content for the body region. |
footer | Named content for the footer 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-search-shell-border | — | var(--ui-overlay-border, var(--ui-border-strong)) |
--ui-search-shell-focus-color | — | var(--ui-control-focus) |
--ui-search-shell-max-height | — | 80vh |
--ui-search-shell-max-width | — | 40rem |
--ui-search-shell-padding-top | — | 10vh |
--ui-search-shell-radius | — | var(--ui-overlay-radius, var(--ui-radius-dialog)) |
--ui-search-shell-shadow | — | var(--ui-overlay-shadow, var(--ui-elevation-dialog, var(--ui-shadow-xl))) |
--ui-search-shell-surface | — | var(--ui-overlay-surface, var(--ui-surface-elevated)) |
--ui-search-shell-viewport-gap | — | var(--ui-space-4) |
--ui-search-shell-z-index | — | 400 |
Shared tokens
| Token | Fallback |
|---|---|
--ui-border-strong | — |
--ui-border-width | — |
--ui-control-border | — |
--ui-control-focus | — |
--ui-control-min-block-size | — |
--ui-control-surface | — |
--ui-elevation-dialog | var(--ui-shadow-xl) |
--ui-field-radius | var(--ui-radius-md) |
--ui-focus-ring | — |
--ui-focus-width | — |
--ui-font-size-sm | — |
--ui-overlay-backdrop | — |
--ui-overlay-border | var(--ui-border-strong) |
--ui-overlay-radius | var(--ui-radius-dialog) |
--ui-overlay-shadow | var(--ui-elevation-dialog, var(--ui-shadow-xl)) |
--ui-overlay-surface | var(--ui-surface-elevated) |
--ui-radius-dialog | — |
--ui-radius-md | — |
--ui-scroll-fade-size | 24px |
--ui-shadow-xl | — |
--ui-shadow-xs | — |
--ui-space-2 | — |
--ui-space-3 | — |
--ui-space-4 | — |
--ui-surface-elevated | — |
--ui-text-primary | — |
--ui-text-secondary | — |
Search Result Row API
Presentational row shell for command/search results. It owns row layout, spacing, hover/selected states, and optional leading/meta/excerpt/trailing regions while apps keep result shaping and highlighting.
Package: @threadlabs/looma · Native root: button
Attributes
| Name | Description | Property | Type | Default | Allowed values |
|---|---|---|---|---|---|
density | Compact keeps a title and location together in a 3.125rem row for small destination pickers; comfortable preserves the existing rich result spacing. | density | keyword | comfortable | comfortable, compact |
disabled | Disables the row's button, so it cannot be chosen or focused, and gives it a flat muted treatment. | disabled | boolean | false | — |
selected | Marks the row as the current result: it gets the hover surface and aria-current="true", which assistive technology announces as current. Set it on one row at a time, such as the result the arrow keys have moved to. | selected | boolean | false | — |
Framework props
| Name | Type | Default | Allowed values |
|---|---|---|---|
density | "comfortable" | "compact" | comfortable | comfortable, compact |
disabled | boolean | false | — |
selected | boolean | false | — |
Events
No events.
Slots
| Name | Purpose |
|---|---|
leading | Named content for the leading region. |
title | Named content for the title region. |
meta | Named content for the meta region. |
excerpt | Named content for the excerpt 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-search-result-row-border | — | transparent |
--ui-search-result-row-gap | — | var(--ui-space-3) | var(--ui-space-2) |
--ui-search-result-row-hover-surface | — | var(--_ui-selection-context-surface, color-mix(in srgb, var(--ui-surface) 85%, black 3%)) |
--ui-search-result-row-min-height | — | 56px | 3.125rem |
--ui-search-result-row-padding-block | — | var(--ui-space-3) | var(--ui-space-1) |
--ui-search-result-row-padding-inline | — | var(--ui-space-4) | var(--ui-space-2) |
--ui-search-result-row-selected-bg | — | var(--_ui-selection-context-surface, var(--ui-selection-surface)) |
--ui-search-result-row-surface | — | transparent |
Shared tokens
| Token | Fallback |
|---|---|
--ui-border-width | — |
--ui-disabled-surface | — |
--ui-disabled-text | — |
--ui-focus-ring | — |
--ui-focus-width | — |
--ui-font-family-sans | sans-serif |
--ui-font-size-sm | — |
--ui-font-size-xs | — |
--ui-line-height | — |
--ui-motion-ease | — |
--ui-motion-fast | — |
--ui-selection-radius | — |
--ui-selection-surface | — |
--ui-selection-text | — |
--ui-space-1 | — |
--ui-space-2 | — |
--ui-space-3 | — |
--ui-space-4 | — |
--ui-surface | — |