Affordance Scope
A lightweight interaction scope that lets nearby controls anticipate pointer movement without adding listeners or hit regions to every control.
Nearby actions
Move toward the guide dots to reveal the actions.
Move toward the actions
Code
<link
rel="component"
href="@threadlabs/looma/components/ui-affordance-scope/ui-affordance-scope.html"
>
<link
rel="component"
href="@threadlabs/looma/components/ui-stack/ui-stack.html"
>
<link
rel="component"
href="@threadlabs/looma/components/ui-text/ui-text.html"
>
<link
rel="component"
href="@threadlabs/looma/components/ui-cluster/ui-cluster.html"
>
<link
rel="component"
href="@threadlabs/looma/components/ui-icon-button/ui-icon-button.html"
>
<link
rel="component"
href="@threadlabs/looma/components/ui-icon/ui-icon.html"
>
<ui-affordance-scope>
<ui-stack
gap="s"
>
<ui-text
variant="caption"
>Move toward the actions</ui-text>
<ui-cluster>
<ui-icon-button
anticipatory
label="Add"
variant="outline"
>
<ui-icon
name="plus"
></ui-icon>
</ui-icon-button>
<ui-icon-button
anticipatory
label="More actions"
variant="outline"
>
<ui-icon
name="ellipsis"
></ui-icon>
</ui-icon-button>
</ui-cluster>
</ui-stack>
</ui-affordance-scope><script setup>
import {
UiAffordanceScope,
UiCluster,
UiIcon,
UiIconButton,
UiStack,
UiText,
} from "@threadlabs/looma";
</script>
<template>
<UiAffordanceScope>
<UiStack
gap="s"
>
<UiText
variant="caption"
>Move toward the actions</UiText>
<UiCluster>
<UiIconButton
:anticipatory="true"
label="Add"
variant="outline"
>
<UiIcon
name="plus"
/>
</UiIconButton>
<UiIconButton
:anticipatory="true"
label="More actions"
variant="outline"
>
<UiIcon
name="ellipsis"
/>
</UiIconButton>
</UiCluster>
</UiStack>
</UiAffordanceScope>
</template>import {
UiAffordanceScope,
UiCluster,
UiIcon,
UiIconButton,
UiStack,
UiText,
} from "@threadlabs/looma";
export default function Example() {
return <UiAffordanceScope>
<UiStack
gap="s"
>
<UiText
variant="caption"
>Move toward the actions</UiText>
<UiCluster>
<UiIconButton
anticipatory
label="Add"
variant="outline"
>
<UiIcon
name="plus"
/>
</UiIconButton>
<UiIconButton
anticipatory
label="More actions"
variant="outline"
>
<UiIcon
name="ellipsis"
/>
</UiIconButton>
</UiCluster>
</UiStack>
</UiAffordanceScope>;
}/** @jsxImportSource preact */
import {
UiAffordanceScope,
UiCluster,
UiIcon,
UiIconButton,
UiStack,
UiText,
} from "@threadlabs/looma";
export default function Example() {
return <UiAffordanceScope>
<UiStack
gap="s"
>
<UiText
variant="caption"
>Move toward the actions</UiText>
<UiCluster>
<UiIconButton
anticipatory
label="Add"
variant="outline"
>
<UiIcon
name="plus"
/>
</UiIconButton>
<UiIconButton
anticipatory
label="More actions"
variant="outline"
>
<UiIcon
name="ellipsis"
/>
</UiIconButton>
</UiCluster>
</UiStack>
</UiAffordanceScope>;
}<script>
import {
UiAffordanceScope,
UiCluster,
UiIcon,
UiIconButton,
UiStack,
UiText,
} from "@threadlabs/looma";
</script>
<UiAffordanceScope>
<UiStack
gap="s"
>
<UiText
variant="caption"
>Move toward the actions</UiText>
<UiCluster>
<UiIconButton
anticipatory
label="Add"
variant="outline"
>
<UiIcon
name="plus"
/>
</UiIconButton>
<UiIconButton
anticipatory
label="More actions"
variant="outline"
>
<UiIcon
name="ellipsis"
/>
</UiIconButton>
</UiCluster>
</UiStack>
</UiAffordanceScope>import { Component } from "@angular/core";
import {
UiAffordanceScope,
UiCluster,
UiIcon,
UiIconButton,
UiStack,
UiText,
} from "@threadlabs/looma";
@Component({
selector: "example-view",
standalone: true,
imports: [UiAffordanceScope, UiStack, UiText, UiCluster, UiIconButton, UiIcon],
template: `
<span
ui-affordance-scope
>
<div
ui-stack
[gap]="'s'"
>
<ng-template
ui-text
[variant]="'caption'"
>Move toward the actions</ng-template>
<div
ui-cluster
>
<button
ui-icon-button
[anticipatory]="true"
[label]="'Add'"
[variant]="'outline'"
>
<span
ui-icon
name="plus"
></span>
</button>
<button
ui-icon-button
[anticipatory]="true"
[label]="'More actions'"
[variant]="'outline'"
>
<span
ui-icon
name="ellipsis"
></span>
</button>
</div>
</div>
</span>
`,
})
export default class Example {}Default radius
At rest each anticipatory button shows a small guide dot. Move the pointer within the default 16px radius to reveal the button.
Code
<link
rel="component"
href="@threadlabs/looma/components/ui-affordance-scope/ui-affordance-scope.html"
>
<link
rel="component"
href="@threadlabs/looma/components/ui-cluster/ui-cluster.html"
>
<link
rel="component"
href="@threadlabs/looma/components/ui-icon-button/ui-icon-button.html"
>
<ui-affordance-scope>
<ui-cluster
gap="xs"
>
<ui-icon-button
anticipatory
variant="outline"
label="Add"
>
<svg
aria-hidden="true"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
>
<path
d="M12 5v14M5 12h14"
></path>
</svg>
</ui-icon-button>
<ui-icon-button
anticipatory
variant="outline"
label="Search"
>
<svg
aria-hidden="true"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
>
<circle
cx="11"
cy="11"
r="7"
></circle>
<path
d="m20 20-3.5-3.5"
></path>
</svg>
</ui-icon-button>
</ui-cluster>
</ui-affordance-scope><script setup>
import { UiAffordanceScope, UiCluster, UiIconButton } from "@threadlabs/looma";
</script>
<template>
<UiAffordanceScope>
<UiCluster
gap="xs"
>
<UiIconButton
:anticipatory="true"
variant="outline"
label="Add"
>
<svg
aria-hidden="true"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
>
<path
d="M12 5v14M5 12h14"
></path>
</svg>
</UiIconButton>
<UiIconButton
:anticipatory="true"
variant="outline"
label="Search"
>
<svg
aria-hidden="true"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
>
<circle
cx="11"
cy="11"
r="7"
></circle>
<path
d="m20 20-3.5-3.5"
></path>
</svg>
</UiIconButton>
</UiCluster>
</UiAffordanceScope>
</template>import { UiAffordanceScope, UiCluster, UiIconButton } from "@threadlabs/looma";
export default function Example() {
return <UiAffordanceScope>
<UiCluster
gap="xs"
>
<UiIconButton
anticipatory
variant="outline"
label="Add"
>
<svg
aria-hidden="true"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
>
<path
d="M12 5v14M5 12h14"
/>
</svg>
</UiIconButton>
<UiIconButton
anticipatory
variant="outline"
label="Search"
>
<svg
aria-hidden="true"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
>
<circle
cx="11"
cy="11"
r="7"
/>
<path
d="m20 20-3.5-3.5"
/>
</svg>
</UiIconButton>
</UiCluster>
</UiAffordanceScope>;
}/** @jsxImportSource preact */
import { UiAffordanceScope, UiCluster, UiIconButton } from "@threadlabs/looma";
export default function Example() {
return <UiAffordanceScope>
<UiCluster
gap="xs"
>
<UiIconButton
anticipatory
variant="outline"
label="Add"
>
<svg
aria-hidden="true"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
>
<path
d="M12 5v14M5 12h14"
/>
</svg>
</UiIconButton>
<UiIconButton
anticipatory
variant="outline"
label="Search"
>
<svg
aria-hidden="true"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
>
<circle
cx="11"
cy="11"
r="7"
/>
<path
d="m20 20-3.5-3.5"
/>
</svg>
</UiIconButton>
</UiCluster>
</UiAffordanceScope>;
}<script>
import { UiAffordanceScope, UiCluster, UiIconButton } from "@threadlabs/looma";
</script>
<UiAffordanceScope>
<UiCluster
gap="xs"
>
<UiIconButton
anticipatory
variant="outline"
label="Add"
>
<svg
aria-hidden="true"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
>
<path
d="M12 5v14M5 12h14"
></path>
</svg>
</UiIconButton>
<UiIconButton
anticipatory
variant="outline"
label="Search"
>
<svg
aria-hidden="true"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
>
<circle
cx="11"
cy="11"
r="7"
></circle>
<path
d="m20 20-3.5-3.5"
></path>
</svg>
</UiIconButton>
</UiCluster>
</UiAffordanceScope>import { Component } from "@angular/core";
import { UiAffordanceScope, UiCluster, UiIconButton } from "@threadlabs/looma";
@Component({
selector: "example-view",
standalone: true,
imports: [UiAffordanceScope, UiCluster, UiIconButton],
template: `
<span
ui-affordance-scope
>
<div
ui-cluster
[gap]="'xs'"
>
<button
ui-icon-button
[anticipatory]="true"
[variant]="'outline'"
[label]="'Add'"
>
<svg
aria-hidden="true"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
>
<path
d="M12 5v14M5 12h14"
></path>
</svg>
</button>
<button
ui-icon-button
[anticipatory]="true"
[variant]="'outline'"
[label]="'Search'"
>
<svg
aria-hidden="true"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
>
<circle
cx="11"
cy="11"
r="7"
></circle>
<path
d="m20 20-3.5-3.5"
></path>
</svg>
</button>
</div>
</span>
`,
})
export default class Example {}near-radius="8"
A smaller near-radius delays anticipatory activation until the pointer is closer.
Code
<link
rel="component"
href="@threadlabs/looma/components/ui-affordance-scope/ui-affordance-scope.html"
>
<link
rel="component"
href="@threadlabs/looma/components/ui-cluster/ui-cluster.html"
>
<link
rel="component"
href="@threadlabs/looma/components/ui-icon-button/ui-icon-button.html"
>
<ui-affordance-scope
near-radius="8"
>
<ui-cluster
gap="xs"
>
<ui-icon-button
anticipatory
variant="outline"
label="Add"
>
<svg
aria-hidden="true"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
>
<path
d="M12 5v14M5 12h14"
></path>
</svg>
</ui-icon-button>
<ui-icon-button
anticipatory
variant="outline"
label="Search"
>
<svg
aria-hidden="true"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
>
<circle
cx="11"
cy="11"
r="7"
></circle>
<path
d="m20 20-3.5-3.5"
></path>
</svg>
</ui-icon-button>
</ui-cluster>
</ui-affordance-scope><script setup>
import { UiAffordanceScope, UiCluster, UiIconButton } from "@threadlabs/looma";
</script>
<template>
<UiAffordanceScope
:near-radius="8"
>
<UiCluster
gap="xs"
>
<UiIconButton
:anticipatory="true"
variant="outline"
label="Add"
>
<svg
aria-hidden="true"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
>
<path
d="M12 5v14M5 12h14"
></path>
</svg>
</UiIconButton>
<UiIconButton
:anticipatory="true"
variant="outline"
label="Search"
>
<svg
aria-hidden="true"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
>
<circle
cx="11"
cy="11"
r="7"
></circle>
<path
d="m20 20-3.5-3.5"
></path>
</svg>
</UiIconButton>
</UiCluster>
</UiAffordanceScope>
</template>import { UiAffordanceScope, UiCluster, UiIconButton } from "@threadlabs/looma";
export default function Example() {
return <UiAffordanceScope
nearRadius={8}
>
<UiCluster
gap="xs"
>
<UiIconButton
anticipatory
variant="outline"
label="Add"
>
<svg
aria-hidden="true"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
>
<path
d="M12 5v14M5 12h14"
/>
</svg>
</UiIconButton>
<UiIconButton
anticipatory
variant="outline"
label="Search"
>
<svg
aria-hidden="true"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
>
<circle
cx="11"
cy="11"
r="7"
/>
<path
d="m20 20-3.5-3.5"
/>
</svg>
</UiIconButton>
</UiCluster>
</UiAffordanceScope>;
}/** @jsxImportSource preact */
import { UiAffordanceScope, UiCluster, UiIconButton } from "@threadlabs/looma";
export default function Example() {
return <UiAffordanceScope
nearRadius={8}
>
<UiCluster
gap="xs"
>
<UiIconButton
anticipatory
variant="outline"
label="Add"
>
<svg
aria-hidden="true"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
>
<path
d="M12 5v14M5 12h14"
/>
</svg>
</UiIconButton>
<UiIconButton
anticipatory
variant="outline"
label="Search"
>
<svg
aria-hidden="true"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
>
<circle
cx="11"
cy="11"
r="7"
/>
<path
d="m20 20-3.5-3.5"
/>
</svg>
</UiIconButton>
</UiCluster>
</UiAffordanceScope>;
}<script>
import { UiAffordanceScope, UiCluster, UiIconButton } from "@threadlabs/looma";
</script>
<UiAffordanceScope
nearRadius={8}
>
<UiCluster
gap="xs"
>
<UiIconButton
anticipatory
variant="outline"
label="Add"
>
<svg
aria-hidden="true"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
>
<path
d="M12 5v14M5 12h14"
></path>
</svg>
</UiIconButton>
<UiIconButton
anticipatory
variant="outline"
label="Search"
>
<svg
aria-hidden="true"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
>
<circle
cx="11"
cy="11"
r="7"
></circle>
<path
d="m20 20-3.5-3.5"
></path>
</svg>
</UiIconButton>
</UiCluster>
</UiAffordanceScope>import { Component } from "@angular/core";
import { UiAffordanceScope, UiCluster, UiIconButton } from "@threadlabs/looma";
@Component({
selector: "example-view",
standalone: true,
imports: [UiAffordanceScope, UiCluster, UiIconButton],
template: `
<span
ui-affordance-scope
[nearRadius]="8"
>
<div
ui-cluster
[gap]="'xs'"
>
<button
ui-icon-button
[anticipatory]="true"
[variant]="'outline'"
[label]="'Add'"
>
<svg
aria-hidden="true"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
>
<path
d="M12 5v14M5 12h14"
></path>
</svg>
</button>
<button
ui-icon-button
[anticipatory]="true"
[variant]="'outline'"
[label]="'Search'"
>
<svg
aria-hidden="true"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
>
<circle
cx="11"
cy="11"
r="7"
></circle>
<path
d="m20 20-3.5-3.5"
></path>
</svg>
</button>
</div>
</span>
`,
})
export default class Example {}guide="none"
No guide dot at rest: each button stays hidden until the pointer comes within the radius, or it is hovered or focused.
Code
<link
rel="component"
href="@threadlabs/looma/components/ui-affordance-scope/ui-affordance-scope.html"
>
<link
rel="component"
href="@threadlabs/looma/components/ui-cluster/ui-cluster.html"
>
<link
rel="component"
href="@threadlabs/looma/components/ui-icon-button/ui-icon-button.html"
>
<ui-affordance-scope
guide="none"
>
<ui-cluster
gap="xs"
>
<ui-icon-button
anticipatory
variant="outline"
label="Add"
>
<svg
aria-hidden="true"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
>
<path
d="M12 5v14M5 12h14"
></path>
</svg>
</ui-icon-button>
<ui-icon-button
anticipatory
variant="outline"
label="Search"
>
<svg
aria-hidden="true"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
>
<circle
cx="11"
cy="11"
r="7"
></circle>
<path
d="m20 20-3.5-3.5"
></path>
</svg>
</ui-icon-button>
</ui-cluster>
</ui-affordance-scope><script setup>
import { UiAffordanceScope, UiCluster, UiIconButton } from "@threadlabs/looma";
</script>
<template>
<UiAffordanceScope
guide="none"
>
<UiCluster
gap="xs"
>
<UiIconButton
:anticipatory="true"
variant="outline"
label="Add"
>
<svg
aria-hidden="true"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
>
<path
d="M12 5v14M5 12h14"
></path>
</svg>
</UiIconButton>
<UiIconButton
:anticipatory="true"
variant="outline"
label="Search"
>
<svg
aria-hidden="true"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
>
<circle
cx="11"
cy="11"
r="7"
></circle>
<path
d="m20 20-3.5-3.5"
></path>
</svg>
</UiIconButton>
</UiCluster>
</UiAffordanceScope>
</template>import { UiAffordanceScope, UiCluster, UiIconButton } from "@threadlabs/looma";
export default function Example() {
return <UiAffordanceScope
guide="none"
>
<UiCluster
gap="xs"
>
<UiIconButton
anticipatory
variant="outline"
label="Add"
>
<svg
aria-hidden="true"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
>
<path
d="M12 5v14M5 12h14"
/>
</svg>
</UiIconButton>
<UiIconButton
anticipatory
variant="outline"
label="Search"
>
<svg
aria-hidden="true"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
>
<circle
cx="11"
cy="11"
r="7"
/>
<path
d="m20 20-3.5-3.5"
/>
</svg>
</UiIconButton>
</UiCluster>
</UiAffordanceScope>;
}/** @jsxImportSource preact */
import { UiAffordanceScope, UiCluster, UiIconButton } from "@threadlabs/looma";
export default function Example() {
return <UiAffordanceScope
guide="none"
>
<UiCluster
gap="xs"
>
<UiIconButton
anticipatory
variant="outline"
label="Add"
>
<svg
aria-hidden="true"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
>
<path
d="M12 5v14M5 12h14"
/>
</svg>
</UiIconButton>
<UiIconButton
anticipatory
variant="outline"
label="Search"
>
<svg
aria-hidden="true"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
>
<circle
cx="11"
cy="11"
r="7"
/>
<path
d="m20 20-3.5-3.5"
/>
</svg>
</UiIconButton>
</UiCluster>
</UiAffordanceScope>;
}<script>
import { UiAffordanceScope, UiCluster, UiIconButton } from "@threadlabs/looma";
</script>
<UiAffordanceScope
guide="none"
>
<UiCluster
gap="xs"
>
<UiIconButton
anticipatory
variant="outline"
label="Add"
>
<svg
aria-hidden="true"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
>
<path
d="M12 5v14M5 12h14"
></path>
</svg>
</UiIconButton>
<UiIconButton
anticipatory
variant="outline"
label="Search"
>
<svg
aria-hidden="true"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
>
<circle
cx="11"
cy="11"
r="7"
></circle>
<path
d="m20 20-3.5-3.5"
></path>
</svg>
</UiIconButton>
</UiCluster>
</UiAffordanceScope>import { Component } from "@angular/core";
import { UiAffordanceScope, UiCluster, UiIconButton } from "@threadlabs/looma";
@Component({
selector: "example-view",
standalone: true,
imports: [UiAffordanceScope, UiCluster, UiIconButton],
template: `
<span
ui-affordance-scope
[guide]="'none'"
>
<div
ui-cluster
[gap]="'xs'"
>
<button
ui-icon-button
[anticipatory]="true"
[variant]="'outline'"
[label]="'Add'"
>
<svg
aria-hidden="true"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
>
<path
d="M12 5v14M5 12h14"
></path>
</svg>
</button>
<button
ui-icon-button
[anticipatory]="true"
[variant]="'outline'"
[label]="'Search'"
>
<svg
aria-hidden="true"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
>
<circle
cx="11"
cy="11"
r="7"
></circle>
<path
d="m20 20-3.5-3.5"
></path>
</svg>
</button>
</div>
</span>
`,
})
export default class Example {}API
Package: @threadlabs/looma · Native root: span
Attributes
| Name | Description | Property | Type | Default | Allowed values |
|---|---|---|---|---|---|
guide | What marks an anticipatory control at rest. dot shows a small guide dot where it will appear; none shows nothing until the pointer nears it, it is hovered, or it takes focus. Every guide inside the scope follows it, including a sidebar's resize line and the editor table's handles. | guide | keyword | dot | dot, none |
near-radius | How close, in pixels, the pointer must come to reveal an affordance. | nearRadius | number | 16 | — |
Framework props
| Name | Type | Default | Allowed values |
|---|---|---|---|
guide | "dot" | "none" | dot | dot, none |
nearRadius | number | 16 | — |
Events
No events.
Slots
| Name | Purpose |
|---|---|
default | Default child content. |
Design tokens
Set shared theme properties on a theme boundary. Component hooks apply only to their owning component.
Component tokens
No component tokens.
Shared tokens
No shared tokens.