Skip to main content
0.79.0
View Zag.js on Github
Join the Discord server

Editable

Editable is an input field used for editing a single line of text. It renders as static text and transforms into a text input field when then edit interaction is triggered (click, focus, or double-click).

Enter text...
Properties

Features

  • Use custom controls for the editable.
  • Pressing Enter commits the input value.
  • Pressing Esc reverts the value.
  • Activate edit mode by double-clicking or focusing on the preview text.
  • Auto-resize input to fit content

Installation

To use the editable machine in your project, run the following command in your command line:

npm install @zag-js/editable @zag-js/react # or yarn add @zag-js/editable @zag-js/react

This command will install the framework agnostic editable logic and the reactive utilities for your framework of choice.

Anatomy

To set up the editable correctly, you'll need to understand its anatomy and how we name its parts.

Each part includes a data-part attribute to help identify them in the DOM.

Usage

First, import the editable package into your project

import * as editable from "@zag-js/editable"

The editable package exports two key functions:

  • machine — The state machine logic for the editable widget.
  • connect — The function that translates the machine's state to JSX attributes and event handlers.

You'll need to provide a unique id to the useMachine hook. This is used to ensure that every part has a unique identifier.

Next, import the required hooks and functions for your framework and use the editable machine in your project 🔥

import * as editable from "@zag-js/editable" import { useMachine, normalizeProps } from "@zag-js/react" export default function Editable() { const [state, send] = useMachine(editable.machine({ id: "1" })) const api = editable.connect(state, send, normalizeProps) return ( <div {...api.getRootProps()}> <div {...api.getAreaProps()}> <input {...api.getInputProps()} /> <span {...api.getPreviewProps()} /> </div> </div> ) }

Setting the initial value

To set the initial value of the editable, pass the value property to the machine's context.

const [state, send] = useMachine( editable.machine({ value: "Hello World", }), )

Listening for value changes

The editable machine supports two ways of listening for value changes:

  • onValueChange: called when value changes.
  • onValueCommit: called when the value is committed.
const [state, send] = useMachine( editable.machine({ onValueChange(details) { console.log("Value changed", details.value) }, onValueCommit(details) { console.log("Value submitted", details.value) }, }), )

Using custom controls

In some cases, you might need to use custom controls to toggle the edit and read mode. We use the render prop pattern to provide access to the internal state of the component.

import * as editable from "@zag-js/editable" import { useMachine } from "@zag-js/react" export default function Editable() { const [state, send] = useMachine(editable.machine({ id: "1" })) const api = editable.connect(state, send) return ( <div {...api.getRootProps()}> <div {...api.getAreaProps()}> <input {...api.getInputProps()} /> <span {...api.getPreviewProps()} /> </div> <div> {!api.editing && <button {...api.getEditTriggerProps()}>Edit</button>} {api.editing && ( <div> <button {...api.getSubmitTriggerProps()}>Save</button> <button {...api.getCancelTriggerProps()}>Cancel</button> </div> )} </div> </div> ) }

Auto-resizing the editable

To auto-grow the editable as the content changes, pass the autoResize: true property to the machine's context.

const [state, send] = useMachine( editable.machine({ autoResize: true, }), )

When using autoresize, the input and preview elements should not have any styles. Use all: unset if needed and pass any styles to the "area" element since its shared by the input and preview elements.

Setting a maxWidth

It is a common pattern to set a maximum of the editable as it auto-grows. To achieve this, set the maxWidth property of the machine's context to the desired value.

const [state, send] = useMachine( editable.machine({ autoResize: true, maxWidth: "320px", }), )

When the editable reaches the specified max-width, it'll clip the preview text with an ellipsis.

Editing with double click

The editable supports two modes of activating the "edit" state:

  • when the preview part is focused (with pointer or keyboard).
  • when the preview part is double-clicked.

To change the mode to "double-click", set the activationMode: 'dblclick' property in the machine's context.

const [state, send] = useMachine( editable.machine({ activationMode: "dblclick", }), )

Usage with Textarea

The editable machine supports using a textarea instead of an input field. When a textarea is used, the editable will commit the value on Cmd + Enter or Ctrl + Enter.

Use the api.inputProps to spread the input props to the textarea element. You might need to cast the input props to the correct type.

<textarea {...(api.inputProps as HTMLTextareaProps<HTMLTextareaElement>)} />

Styling guide

Earlier, we mentioned that each editable part has a data-part attribute added to them to select and style them in the DOM.

Focused state

When the editable is in the focused mode, we set a data-focus attribute on the "area" part.

[data-part="area"][data-focus] { /* CSS for the editable's focus state */ }

Empty state

When the editable's value is empty, we set a data-empty attribute on the "area" part.

[data-part="area"][data-empty] { /* CSS for the editable's focus state */ }

Disabled state

When the editable is disabled, we set a data-disabled attribute on the "area" part.

[data-part="area"][data-disabled] { /* CSS for the editable's focus state */ }

Methods and Properties

Machine Context

The editable machine exposes the following context properties:

  • idsPartial<{ root: string; area: string; label: string; preview: string; input: string; control: string; submitTrigger: string; cancelTrigger: string; editTrigger: string; }>The ids of the elements in the editable. Useful for composition.
  • invalidbooleanWhether the input's value is invalid.
  • namestringThe name attribute of the editable component. Used for form submission.
  • formstringThe associate form of the underlying input.
  • autoResizebooleanWhether the editable should auto-resize to fit the content.
  • activationModeActivationModeThe activation mode for the preview element. - "focus" - Enter edit mode when the preview is focused - "dblclick" - Enter edit mode when the preview is double-clicked - "click" - Enter edit mode when the preview is clicked
  • submitModeSubmitModeThe action that triggers submit in the edit mode: - "enter" - Trigger submit when the enter key is pressed - "blur" - Trigger submit when the editable is blurred - "none" - No action will trigger submit. You need to use the submit button - "both" - Pressing `Enter` and blurring the input will trigger submit
  • editbooleanWhether the editable is in edit mode.
  • edit.controlledbooleanWhether the editable is controlled
  • selectOnFocusbooleanWhether to select the text in the input when it is focused.
  • valuestringThe value of the editable in both edit and preview mode
  • maxLengthnumberThe maximum number of characters allowed in the editable
  • disabledbooleanWhether the editable is disabled
  • readOnlybooleanWhether the editable is readonly
  • requiredbooleanWhether the editable is required
  • onValueChange(details: ValueChangeDetails) => voidThe callback that is called when the editable's value is changed
  • onValueRevert(details: ValueChangeDetails) => voidThe callback that is called when the esc key is pressed or the cancel button is clicked
  • onValueCommit(details: ValueChangeDetails) => voidThe callback that is called when the editable's value is submitted.
  • onEditChange(details: EditChangeDetails) => voidThe callback that is called when the edit mode is changed
  • placeholderstring | { edit: string; preview: string; }The placeholder value to show when the `value` is empty
  • translationsIntlTranslationsSpecifies the localized strings that identifies the accessibility elements and their states
  • finalFocusEl() => HTMLElementThe element that should receive focus when the editable is closed. By default, it will focus on the trigger element.
  • dir"ltr" | "rtl"The document's text/writing direction.
  • idstringThe unique identifier of the machine.
  • getRootNode() => Node | ShadowRoot | DocumentA root node to correctly resolve document in custom environments. E.x.: Iframes, Electron.
  • onPointerDownOutside(event: PointerDownOutsideEvent) => voidFunction called when the pointer is pressed down outside the component
  • onFocusOutside(event: FocusOutsideEvent) => voidFunction called when the focus is moved outside the component
  • onInteractOutside(event: InteractOutsideEvent) => voidFunction called when an interaction happens outside the component

Machine API

The editable api exposes the following methods:

  • editingbooleanWhether the editable is in edit mode
  • emptybooleanWhether the editable value is empty
  • valuestringThe current value of the editable
  • valueTextstringThe current value of the editable, or the placeholder if the value is empty
  • setValue(value: string) => voidFunction to set the value of the editable
  • clearValue() => voidFunction to clear the value of the editable
  • edit() => voidFunction to enter edit mode
  • cancel() => voidFunction to exit edit mode, and discard any changes
  • submit() => voidFunction to exit edit mode, and submit any changes

Data Attributes

Area
data-scope
editable
data-part
area
data-focus
Present when focused
data-disabled
Present when disabled
data-placeholder-shown
Present when placeholder is shown
Label
data-scope
editable
data-part
label
data-focus
Present when focused
data-invalid
Present when invalid
Input
data-scope
editable
data-part
input
data-disabled
Present when disabled
data-readonly
Present when read-only
data-invalid
Present when invalid
Preview
data-scope
editable
data-part
preview
data-placeholder-shown
Present when placeholder is shown
data-readonly
Present when read-only
data-disabled
Present when disabled
data-invalid
Present when invalid

Accessibility

Keyboard Interactions

  • Enter
    Saves the edited content and exits edit mode.
  • Escape
    Discards the changes and exits edit mode.

Edit this page on GitHub

Proudly made in🇳🇬by Segun Adebayo

Copyright © 2024
On this page