Editable

Reveal an editing control from an explicit presentation trigger.

Default

Click the text (or focus it and press Enter) to edit it in place. Enter or clicking away saves; Escape cancels. The control keeps the same size in both states.

Code

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

<ui-editable
  value="Editable text"
  label="Project title"
> </ui-editable>
<script setup>
  import { UiEditable } from "@threadlabs/looma";
</script>

<template>
  <UiEditable
    value="Editable text"
    label="Project title"
  > </UiEditable>
</template>
import { UiEditable } from "@threadlabs/looma";

export default function Example() {
  return <UiEditable
    value="Editable text"
    label="Project title"
  > </UiEditable>;
}
/** @jsxImportSource preact */
import { UiEditable } from "@threadlabs/looma";

export default function Example() {
  return <UiEditable
    value="Editable text"
    label="Project title"
  > </UiEditable>;
}
<script>
  import { UiEditable } from "@threadlabs/looma";
</script>

<UiEditable
  value="Editable text"
  label="Project title"
> </UiEditable>
import { Component } from "@angular/core";
import { UiEditable } from "@threadlabs/looma";

@Component({
  selector: "example-view",
  standalone: true,
  imports: [UiEditable],
  template: `
    <div
      ui-editable
      [value]="'Editable text'"
      [label]="'Project title'"
    > </div>
  `,
})
export default class Example {}

hint="Rename"

Hint adds visible text beside the value to signal that it can be edited. Nothing is shown unless you ask for it.

Code

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

<ui-editable
  value="Quarterly roadmap"
  label="Document title"
  hint="Rename"
> </ui-editable>
<script setup>
  import { UiEditable } from "@threadlabs/looma";
</script>

<template>
  <UiEditable
    value="Quarterly roadmap"
    label="Document title"
    hint="Rename"
  > </UiEditable>
</template>
import { UiEditable } from "@threadlabs/looma";

export default function Example() {
  return <UiEditable
    value="Quarterly roadmap"
    label="Document title"
    hint="Rename"
  > </UiEditable>;
}
/** @jsxImportSource preact */
import { UiEditable } from "@threadlabs/looma";

export default function Example() {
  return <UiEditable
    value="Quarterly roadmap"
    label="Document title"
    hint="Rename"
  > </UiEditable>;
}
<script>
  import { UiEditable } from "@threadlabs/looma";
</script>

<UiEditable
  value="Quarterly roadmap"
  label="Document title"
  hint="Rename"
> </UiEditable>
import { Component } from "@angular/core";
import { UiEditable } from "@threadlabs/looma";

@Component({
  selector: "example-view",
  standalone: true,
  imports: [UiEditable],
  template: `
    <div
      ui-editable
      [value]="'Quarterly roadmap'"
      [label]="'Document title'"
      [hint]="'Rename'"
    > </div>
  `,
})
export default class Example {}

actions

Actions shows explicit Save and Cancel buttons while editing, for cases where an implicit save on Enter or blur is not enough.

Code

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

<ui-editable
  value="Quarterly roadmap"
  label="Document title"
  actions
> </ui-editable>
<script setup>
  import { UiEditable } from "@threadlabs/looma";
</script>

<template>
  <UiEditable
    value="Quarterly roadmap"
    label="Document title"
    :actions="true"
  > </UiEditable>
</template>
import { UiEditable } from "@threadlabs/looma";

export default function Example() {
  return <UiEditable
    value="Quarterly roadmap"
    label="Document title"
    actions
  > </UiEditable>;
}
/** @jsxImportSource preact */
import { UiEditable } from "@threadlabs/looma";

export default function Example() {
  return <UiEditable
    value="Quarterly roadmap"
    label="Document title"
    actions
  > </UiEditable>;
}
<script>
  import { UiEditable } from "@threadlabs/looma";
</script>

<UiEditable
  value="Quarterly roadmap"
  label="Document title"
  actions
> </UiEditable>
import { Component } from "@angular/core";
import { UiEditable } from "@threadlabs/looma";

@Component({
  selector: "example-view",
  standalone: true,
  imports: [UiEditable],
  template: `
    <div
      ui-editable
      [value]="'Quarterly roadmap'"
      [label]="'Document title'"
      [actions]="true"
    > </div>
  `,
})
export default class Example {}

edit and disabled

Edit starts in edit mode; disabled prevents entering it.

Code

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

<ui-stack
  gap="s"
  align="start"
>
  <ui-editable
    value="Editing now"
    label="Title"
    edit
  > </ui-editable>
  <ui-editable
    value="Locked title"
    label="Locked title"
    disabled
  > </ui-editable>
</ui-stack>
<script setup>
  import { UiEditable, UiStack } from "@threadlabs/looma";
</script>

<template>
  <UiStack
    gap="s"
    align="start"
  >
    <UiEditable
      value="Editing now"
      label="Title"
      :edit="true"
    > </UiEditable>
    <UiEditable
      value="Locked title"
      label="Locked title"
      :disabled="true"
    > </UiEditable>
  </UiStack>
</template>
import { UiEditable, UiStack } from "@threadlabs/looma";

export default function Example() {
  return <UiStack
    gap="s"
    align="start"
  >
    <UiEditable
      value="Editing now"
      label="Title"
      edit
    > </UiEditable>
    <UiEditable
      value="Locked title"
      label="Locked title"
      disabled
    > </UiEditable>
  </UiStack>;
}
/** @jsxImportSource preact */
import { UiEditable, UiStack } from "@threadlabs/looma";

export default function Example() {
  return <UiStack
    gap="s"
    align="start"
  >
    <UiEditable
      value="Editing now"
      label="Title"
      edit
    > </UiEditable>
    <UiEditable
      value="Locked title"
      label="Locked title"
      disabled
    > </UiEditable>
  </UiStack>;
}
<script>
  import { UiEditable, UiStack } from "@threadlabs/looma";
</script>

<UiStack
  gap="s"
  align="start"
>
  <UiEditable
    value="Editing now"
    label="Title"
    edit
  > </UiEditable>
  <UiEditable
    value="Locked title"
    label="Locked title"
    disabled
  > </UiEditable>
</UiStack>
import { Component } from "@angular/core";
import { UiEditable, UiStack } from "@threadlabs/looma";

@Component({
  selector: "example-view",
  standalone: true,
  imports: [UiStack, UiEditable],
  template: `
    <div
      ui-stack
      [gap]="'s'"
      [align]="'start'"
    >
      <div
        ui-editable
        [value]="'Editing now'"
        [label]="'Title'"
        [edit]="true"
      > </div>
      <div
        ui-editable
        [value]="'Locked title'"
        [label]="'Locked title'"
        [disabled]="true"
      > </div>
    </div>
  `,
})
export default class Example {}

Required short text

Native required and maximum-length constraints keep an invalid draft open. Action labels and the empty placeholder can match the field.

Code

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

<ui-editable
  label="Short name"
  value="Untitled"
  actions
  required
  max-length="24"
  placeholder="Enter a short name"
  save-label="Apply"
  cancel-label="Discard"
></ui-editable>
<script setup>
  import { UiEditable } from "@threadlabs/looma";
</script>

<template>
  <UiEditable
    label="Short name"
    value="Untitled"
    :actions="true"
    :required="true"
    :max-length="24"
    placeholder="Enter a short name"
    save-label="Apply"
    cancel-label="Discard"
  />
</template>
import { UiEditable } from "@threadlabs/looma";

export default function Example() {
  return <UiEditable
    label="Short name"
    value="Untitled"
    actions
    required
    maxLength={24}
    placeholder="Enter a short name"
    saveLabel="Apply"
    cancelLabel="Discard"
  />;
}
/** @jsxImportSource preact */
import { UiEditable } from "@threadlabs/looma";

export default function Example() {
  return <UiEditable
    label="Short name"
    value="Untitled"
    actions
    required
    maxLength={24}
    placeholder="Enter a short name"
    saveLabel="Apply"
    cancelLabel="Discard"
  />;
}
<script>
  import { UiEditable } from "@threadlabs/looma";
</script>

<UiEditable
  label="Short name"
  value="Untitled"
  actions
  required
  maxLength={24}
  placeholder="Enter a short name"
  saveLabel="Apply"
  cancelLabel="Discard"
/>
import { Component } from "@angular/core";
import { UiEditable } from "@threadlabs/looma";

@Component({
  selector: "example-view",
  standalone: true,
  imports: [UiEditable],
  template: `
    <div
      ui-editable
      [label]="'Short name'"
      [value]="'Untitled'"
      [actions]="true"
      [required]="true"
      [maxLength]="24"
      [placeholder]="'Enter a short name'"
      [saveLabel]="'Apply'"
      [cancelLabel]="'Discard'"
    ></div>
  `,
})
export default class Example {}

API

Package: @threadlabs/looma · Native root: div

Attributes

NameDescriptionPropertyTypeDefaultAllowed values
actionsShow explicit Save and Cancel buttons while editing. Without them, Enter or leaving the field saves and Escape cancels.actionsbooleanfalse—
cancel-labelVisible text for the Cancel action.cancelLabelstringCancel—
disabledPrevents entering or changing edit mode.disabledbooleanfalse—
editWhether edit mode is active initially or when set externally.editbooleanfalse—
hintOptional text shown beside the value to signal it is editable (for example "Edit").hintstring—
labelAccessible name for the editor.labelstringEdit value—
max-lengthMaximum draft length; 0 leaves the length unrestricted.maxLengthnumber0—
placeholderHint shown in the text input while the draft is empty.placeholderstring—
requiredPrevent an empty draft from being saved.requiredbooleanfalse—
save-labelVisible text for the Save action.saveLabelstringSave—
valueThe displayed value, and the text the editor starts from. It is not a form field: nothing is submitted with a form, so a form that needs the value copies it from the change event into a field of its own.valuestring—

Framework props

NameTypeDefaultAllowed values
actionsbooleanfalse—
cancelLabelstringCancel—
disabledbooleanfalse—
editbooleanfalse—
hintstring—
labelstringEdit value—
maxLengthnumber0—
placeholderstring—
requiredbooleanfalse—
saveLabelstringSave—
valuestring—

Events

NameDetailDescription
changeobject
  • value: string (required)
  • previousValue: string (required)
  • trigger: keyword (required)
Emitted when a toggleable control changes checked state.
edit-changeobject
  • edit: boolean (required)
  • reason: keyword (required)
  • trigger: keyword (required)
Emitted with the declared detail payload.
inputobject
  • value: string (required)
  • trigger: keyword (required)
Emitted with the declared detail payload.

Slots

No slots.

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

TokenFallback
--ui-accent-solid—
--ui-border-strong—
--ui-border-width—
--ui-control-focusvar(--ui-accent-solid)
--ui-control-focus-halo-shadow—
--ui-control-inset—
--ui-control-size-sm—
--ui-control-surfacevar(--ui-surface-elevated)
--ui-disabled-text—
--ui-focus-halo—
--ui-font-size-sm—
--ui-line-height-md—
--ui-motion-ease—
--ui-motion-fast—
--ui-radius-md—
--ui-raised—
--ui-raised-highlight—
--ui-space-1—
--ui-space-2—
--ui-space-3—
--ui-surface-elevated—
--ui-surface-muted—
--ui-text-on-accent—
--ui-text-primary—
--ui-text-secondary—