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.

Unsaved changes

You have edits that are not saved yet.

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

NamePurpose
tertiaryNamed content for the tertiary region.
secondaryNamed content for the secondary region.
defaultDefault child content.
primaryNamed 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

TokenDeclared valuesFallback
--ui-action-bar-gapvar(--ui-space-2)—

Shared tokens

TokenFallback
--ui-space-2—