Action Bar
The action row at the foot of a form or dialog. It places each action by what it does, so every form in a product ends the same way and no one hand-aligns buttons.
Form actions
Secondary and primary actions in a compact footer.
Code
<link
rel="component"
href="@threadlabs/looma/components/ui-action-bar/ui-action-bar.html"
>
<link
rel="component"
href="@threadlabs/looma/components/ui-button/ui-button.html"
>
<ui-action-bar>
<ui-button
slot="secondary"
size="sm"
>Cancel</ui-button>
<ui-button
slot="primary"
size="sm"
preset="primary"
>Save</ui-button>
</ui-action-bar><script setup>
import { UiActionBar, UiButton } from "@threadlabs/looma";
</script>
<template>
<UiActionBar>
<template #secondary>
<UiButton
size="sm"
>Cancel</UiButton>
</template>
<template #primary>
<UiButton
size="sm"
preset="primary"
>Save</UiButton>
</template>
</UiActionBar>
</template>import { UiActionBar, UiButton } from "@threadlabs/looma";
export default function Example() {
return <UiActionBar
slots={{
secondary: <UiButton
size="sm"
>Cancel</UiButton>,
primary: <UiButton
size="sm"
preset="primary"
>Save</UiButton>,
}}
/>;
}/** @jsxImportSource preact */
import { UiActionBar, UiButton } from "@threadlabs/looma";
export default function Example() {
return <UiActionBar
slots={{
secondary: <UiButton
size="sm"
>Cancel</UiButton>,
primary: <UiButton
size="sm"
preset="primary"
>Save</UiButton>,
}}
/>;
}<script>
import { UiActionBar, UiButton } from "@threadlabs/looma";
</script>
{#snippet secondary()}
<UiButton
size="sm"
>Cancel</UiButton>
{/snippet}
{#snippet primary()}
<UiButton
size="sm"
preset="primary"
>Save</UiButton>
{/snippet}
<UiActionBar
slots={{ secondary, primary }}
/>import { Component } from "@angular/core";
import { UiActionBar, UiButton } from "@threadlabs/looma";
@Component({
selector: "example-view",
standalone: true,
imports: [UiActionBar, UiButton],
template: `
<div
ui-action-bar
>
<ng-container ngProjectAs="[slot='secondary']">
<ng-template
ui-button
[size]="'sm'"
>Cancel</ng-template>
</ng-container>
<ng-container ngProjectAs="[slot='primary']">
<ng-template
ui-button
[size]="'sm'"
[preset]="'primary'"
>Save</ng-template>
</ng-container>
</div>
`,
})
export default class Example {}Form footer
The primary action ends the row as the one solid button, the secondary action sits just before it in outline, and the tertiary action waits at the start edge as a ghost button. The bar only places them; each button keeps its own variant and tone.
Code
<link
rel="component"
href="@threadlabs/looma/components/ui-stack/ui-stack.html"
>
<link
rel="component"
href="@threadlabs/looma/components/ui-form-field/ui-form-field.html"
>
<link
rel="component"
href="@threadlabs/looma/components/ui-input/ui-input.html"
>
<link
rel="component"
href="@threadlabs/looma/components/ui-action-bar/ui-action-bar.html"
>
<link
rel="component"
href="@threadlabs/looma/components/ui-button/ui-button.html"
>
<form>
<ui-stack>
<ui-form-field>
<label
slot="label"
>Title</label>
<ui-input></ui-input>
</ui-form-field>
<ui-action-bar>
<ui-button
slot="tertiary"
variant="ghost"
tone="neutral"
>Cancel</ui-button>
<ui-button
slot="secondary"
>Save as draft</ui-button>
<ui-button
slot="primary"
variant="solid"
type="submit"
>Publish</ui-button>
</ui-action-bar>
</ui-stack>
</form><script setup>
import { UiActionBar, UiButton, UiFormField, UiInput, UiStack } from "@threadlabs/looma";
</script>
<template>
<form>
<UiStack>
<UiFormField>
<template #label>
<label>Title</label>
</template>
<UiInput />
</UiFormField>
<UiActionBar>
<template #tertiary>
<UiButton
variant="ghost"
tone="neutral"
>Cancel</UiButton>
</template>
<template #secondary>
<UiButton>Save as draft</UiButton>
</template>
<template #primary>
<UiButton
variant="solid"
type="submit"
>Publish</UiButton>
</template>
</UiActionBar>
</UiStack>
</form>
</template>import { UiActionBar, UiButton, UiFormField, UiInput, UiStack } from "@threadlabs/looma";
export default function Example() {
return <form>
<UiStack>
<UiFormField
slots={{
label: <label>Title</label>,
}}
>
<UiInput />
</UiFormField>
<UiActionBar
slots={{
tertiary: <UiButton
variant="ghost"
tone="neutral"
>Cancel</UiButton>,
secondary: <UiButton>Save as draft</UiButton>,
primary: <UiButton
variant="solid"
type="submit"
>Publish</UiButton>,
}}
>{" "}{" "}{" "}{" "}</UiActionBar>
</UiStack>
</form>;
}/** @jsxImportSource preact */
import { UiActionBar, UiButton, UiFormField, UiInput, UiStack } from "@threadlabs/looma";
export default function Example() {
return <form>
<UiStack>
<UiFormField
slots={{
label: <label>Title</label>,
}}
>
<UiInput />
</UiFormField>
<UiActionBar
slots={{
tertiary: <UiButton
variant="ghost"
tone="neutral"
>Cancel</UiButton>,
secondary: <UiButton>Save as draft</UiButton>,
primary: <UiButton
variant="solid"
type="submit"
>Publish</UiButton>,
}}
>{" "}{" "}{" "}{" "}</UiActionBar>
</UiStack>
</form>;
}<script>
import { UiActionBar, UiButton, UiFormField, UiInput, UiStack } from "@threadlabs/looma";
</script>
{#snippet label()}
<label>Title</label>
{/snippet}
{#snippet tertiary()}
<UiButton
variant="ghost"
tone="neutral"
>Cancel</UiButton>
{/snippet}
{#snippet secondary()}
<UiButton>Save as draft</UiButton>
{/snippet}
{#snippet primary()}
<UiButton
variant="solid"
type="submit"
>Publish</UiButton>
{/snippet}
<form>
<UiStack>
<UiFormField
slots={{ label }}
>
<UiInput />
</UiFormField>
<UiActionBar
slots={{ tertiary, secondary, primary }}
> </UiActionBar>
</UiStack>
</form>import { Component } from "@angular/core";
import { UiActionBar, UiButton, UiFormField, UiInput, UiStack } from "@threadlabs/looma";
@Component({
selector: "example-view",
standalone: true,
imports: [UiStack, UiFormField, UiInput, UiActionBar, UiButton],
template: `
<form>
<div
ui-stack
>
<div
ui-form-field
>
<ng-container ngProjectAs="[slot='label']">
<label>Title</label>
</ng-container>
<input
ui-input
>
</div>
<div
ui-action-bar
>
<ng-container ngProjectAs="[slot='tertiary']">
<ng-template
ui-button
[variant]="'ghost'"
[tone]="'neutral'"
>Cancel</ng-template>
</ng-container>
<ng-container ngProjectAs="[slot='secondary']">
<ng-template
ui-button
>Save as draft</ng-template>
</ng-container>
<ng-container ngProjectAs="[slot='primary']">
<ng-template
ui-button
[variant]="'solid'"
[type]="'submit'"
>Publish</ng-template>
</ng-container>
</div>
</div>
</form>
`,
})
export default class Example {}Dialog footer
In a dialog's actions slot the bar fills the footer, so a safe destructive action can wait at the start edge, away from the primary action at the end.
Code
<link
rel="component"
href="@threadlabs/looma/components/ui-button/ui-button.html"
>
<link
rel="component"
href="@threadlabs/looma/components/ui-dialog/ui-dialog.html"
>
<link
rel="component"
href="@threadlabs/looma/components/ui-action-bar/ui-action-bar.html"
>
<ui-button
id="open-action-bar-dialog"
>Open dialog</ui-button>
<ui-dialog
id="action-bar-dialog"
for="open-action-bar-dialog"
label="Unsaved changes"
>
<p>You have edits that are not saved yet.</p>
<ui-action-bar
slot="actions"
>
<ui-button
slot="tertiary"
variant="ghost"
tone="danger"
commandfor="action-bar-dialog"
command="close"
>Discard</ui-button>
<ui-button
slot="secondary"
commandfor="action-bar-dialog"
command="close"
>Keep editing</ui-button>
<ui-button
slot="primary"
variant="solid"
commandfor="action-bar-dialog"
command="close"
>Save</ui-button>
</ui-action-bar>
</ui-dialog><script setup>
import { UiActionBar, UiButton, UiDialog } from "@threadlabs/looma";
</script>
<template>
<UiButton
id="open-action-bar-dialog"
>Open dialog</UiButton>
<UiDialog
id="action-bar-dialog"
for="open-action-bar-dialog"
label="Unsaved changes"
>
<p>You have edits that are not saved yet.</p>
<template #actions>
<UiActionBar>
<template #tertiary>
<UiButton
variant="ghost"
tone="danger"
commandfor="action-bar-dialog"
command="close"
>Discard</UiButton>
</template>
<template #secondary>
<UiButton
commandfor="action-bar-dialog"
command="close"
>Keep editing</UiButton>
</template>
<template #primary>
<UiButton
variant="solid"
commandfor="action-bar-dialog"
command="close"
>Save</UiButton>
</template>
</UiActionBar>
</template>
</UiDialog>
</template>import { UiActionBar, UiButton, UiDialog } from "@threadlabs/looma";
export default function Example() {
return <>
<UiButton
id="open-action-bar-dialog"
>Open dialog</UiButton>
<UiDialog
id="action-bar-dialog"
htmlFor="open-action-bar-dialog"
label="Unsaved changes"
slots={{
actions: <UiActionBar
slots={{
tertiary: <UiButton
variant="ghost"
tone="danger"
commandfor="action-bar-dialog"
command="close"
>Discard</UiButton>,
secondary: <UiButton
commandfor="action-bar-dialog"
command="close"
>Keep editing</UiButton>,
primary: <UiButton
variant="solid"
commandfor="action-bar-dialog"
command="close"
>Save</UiButton>,
}}
>{" "}{" "}{" "}{" "}</UiActionBar>,
}}
>
<p>You have edits that are not saved yet.</p>
</UiDialog>
</>;
}/** @jsxImportSource preact */
import { UiActionBar, UiButton, UiDialog } from "@threadlabs/looma";
export default function Example() {
return <>
<UiButton
id="open-action-bar-dialog"
>Open dialog</UiButton>
<UiDialog
id="action-bar-dialog"
htmlFor="open-action-bar-dialog"
label="Unsaved changes"
slots={{
actions: <UiActionBar
slots={{
tertiary: <UiButton
variant="ghost"
tone="danger"
commandfor="action-bar-dialog"
command="close"
>Discard</UiButton>,
secondary: <UiButton
commandfor="action-bar-dialog"
command="close"
>Keep editing</UiButton>,
primary: <UiButton
variant="solid"
commandfor="action-bar-dialog"
command="close"
>Save</UiButton>,
}}
>{" "}{" "}{" "}{" "}</UiActionBar>,
}}
>
<p>You have edits that are not saved yet.</p>
</UiDialog>
</>;
}<script>
import { UiActionBar, UiButton, UiDialog } from "@threadlabs/looma";
</script>
{#snippet actions()}
<UiActionBar
slots={{ tertiary, secondary, primary }}
> </UiActionBar>
{/snippet}
{#snippet tertiary()}
<UiButton
variant="ghost"
tone="danger"
commandfor="action-bar-dialog"
command="close"
>Discard</UiButton>
{/snippet}
{#snippet secondary()}
<UiButton
commandfor="action-bar-dialog"
command="close"
>Keep editing</UiButton>
{/snippet}
{#snippet primary()}
<UiButton
variant="solid"
commandfor="action-bar-dialog"
command="close"
>Save</UiButton>
{/snippet}
<UiButton
id="open-action-bar-dialog"
>Open dialog</UiButton>
<UiDialog
id="action-bar-dialog"
for="open-action-bar-dialog"
label="Unsaved changes"
slots={{ actions }}
>
<p>You have edits that are not saved yet.</p>
</UiDialog>import { Component } from "@angular/core";
import { UiActionBar, UiButton, UiDialog } from "@threadlabs/looma";
@Component({
selector: "example-view",
standalone: true,
imports: [UiButton, UiDialog, UiActionBar],
template: `
<ng-template
ui-button
[hnAttributes]="{ id: 'open-action-bar-dialog' }"
>Open dialog</ng-template>
<dialog
ui-dialog
id="action-bar-dialog"
[for]="'open-action-bar-dialog'"
[label]="'Unsaved changes'"
>
<p>You have edits that are not saved yet.</p>
<ng-container ngProjectAs="[slot='actions']">
<div
ui-action-bar
>
<ng-container ngProjectAs="[slot='tertiary']">
<ng-template
ui-button
[variant]="'ghost'"
[tone]="'danger'"
[hnAttributes]="{ commandfor: 'action-bar-dialog', command: 'close' }"
>Discard</ng-template>
</ng-container>
<ng-container ngProjectAs="[slot='secondary']">
<ng-template
ui-button
[hnAttributes]="{ commandfor: 'action-bar-dialog', command: 'close' }"
>Keep editing</ng-template>
</ng-container>
<ng-container ngProjectAs="[slot='primary']">
<ng-template
ui-button
[variant]="'solid'"
[hnAttributes]="{ commandfor: 'action-bar-dialog', command: 'close' }"
>Save</ng-template>
</ng-container>
</div>
</ng-container>
</dialog>
`,
})
export default class Example {}Narrow container
Below 24rem of its own width, not the viewport's, the bar stacks its actions full width with the primary action on top, then secondary, then tertiary. Tab order stays tertiary, secondary, primary at every width.
Code
<link
rel="component"
href="@threadlabs/looma/components/ui-action-bar/ui-action-bar.html"
>
<link
rel="component"
href="@threadlabs/looma/components/ui-button/ui-button.html"
>
<div
style="max-inline-size: 18rem"
>
<ui-action-bar>
<ui-button
slot="tertiary"
variant="ghost"
tone="neutral"
>Back</ui-button>
<ui-button
slot="secondary"
>Preview</ui-button>
<ui-button
slot="primary"
variant="solid"
>Send</ui-button>
</ui-action-bar>
</div><script setup>
import { UiActionBar, UiButton } from "@threadlabs/looma";
</script>
<template>
<div
style="max-inline-size: 18rem"
>
<UiActionBar>
<template #tertiary>
<UiButton
variant="ghost"
tone="neutral"
>Back</UiButton>
</template>
<template #secondary>
<UiButton>Preview</UiButton>
</template>
<template #primary>
<UiButton
variant="solid"
>Send</UiButton>
</template>
</UiActionBar>
</div>
</template>import { UiActionBar, UiButton } from "@threadlabs/looma";
export default function Example() {
return <div
style={{"maxInlineSize":"18rem"}}
>
<UiActionBar
slots={{
tertiary: <UiButton
variant="ghost"
tone="neutral"
>Back</UiButton>,
secondary: <UiButton>Preview</UiButton>,
primary: <UiButton
variant="solid"
>Send</UiButton>,
}}
>{" "}{" "}{" "}{" "}</UiActionBar>
</div>;
}/** @jsxImportSource preact */
import { UiActionBar, UiButton } from "@threadlabs/looma";
export default function Example() {
return <div
style={{"maxInlineSize":"18rem"}}
>
<UiActionBar
slots={{
tertiary: <UiButton
variant="ghost"
tone="neutral"
>Back</UiButton>,
secondary: <UiButton>Preview</UiButton>,
primary: <UiButton
variant="solid"
>Send</UiButton>,
}}
>{" "}{" "}{" "}{" "}</UiActionBar>
</div>;
}<script>
import { UiActionBar, UiButton } from "@threadlabs/looma";
</script>
{#snippet tertiary()}
<UiButton
variant="ghost"
tone="neutral"
>Back</UiButton>
{/snippet}
{#snippet secondary()}
<UiButton>Preview</UiButton>
{/snippet}
{#snippet primary()}
<UiButton
variant="solid"
>Send</UiButton>
{/snippet}
<div
style="max-inline-size: 18rem"
>
<UiActionBar
slots={{ tertiary, secondary, primary }}
> </UiActionBar>
</div>import { Component } from "@angular/core";
import { UiActionBar, UiButton } from "@threadlabs/looma";
@Component({
selector: "example-view",
standalone: true,
imports: [UiActionBar, UiButton],
template: `
<div
style="max-inline-size: 18rem"
>
<div
ui-action-bar
>
<ng-container ngProjectAs="[slot='tertiary']">
<ng-template
ui-button
[variant]="'ghost'"
[tone]="'neutral'"
>Back</ng-template>
</ng-container>
<ng-container ngProjectAs="[slot='secondary']">
<ng-template
ui-button
>Preview</ng-template>
</ng-container>
<ng-container ngProjectAs="[slot='primary']">
<ng-template
ui-button
[variant]="'solid'"
>Send</ng-template>
</ng-container>
</div>
</div>
`,
})
export default class Example {}API
Package: @threadlabs/looma · Native root: div
Attributes
No attributes.
Framework props
No framework props.
Events
No events.
Slots
| Name | Purpose |
|---|---|
tertiary | Named content for the tertiary region. |
secondary | Named content for the secondary region. |
default | Default child content. |
primary | Named content for the primary 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-action-bar-gap | var(--ui-space-2) | — |
Shared tokens
| Token | Fallback |
|---|---|
--ui-space-2 | — |