Easing editor

Edits one easing curve on a graph with Bézier handles, custom points or spring parameters, with presets, a duration, a live preview and export adapters.

<tp-easing-editor>
Easing editor
Default density Compact density Restart from the initial state Show the copyable source

Usage

The value is an easing string in the canonical notation of its kind: cubic-bezier(), steps() and linear() for CSS curves, spring(mass stiffness damping) for springs, and sine(out), power(in-out 3) or back(out 1.702) for the classic families. Any of these, or a CSS keyword such as ease-in-out, goes in; value always reads back canonical. Give the editor an inline size: the extended layout grows to the graph plus the presets column and no further, and the compact layout fills up to 32rem.

The Ease and Spring tabs switch the category, not the kind: switching converts the curve, and the badge over the graph names the matching preset or reads Custom once a handle leaves every preset. Progression is never clamped, so curves may overshoot; the graph widens its range to keep them visible. Switching to Spring converts the curve to the default spring, switching to Ease fits a cubic Bézier to it, both as a proposal on the value lane with conversion: true in the metadata; a rejected conversion keeps the tab where it was. An empty editor starts from ease or spring(1 100 10). A sine, power, expo, circ or back curve shows the handles of its Bézier fit from the start: dragging or stepping one converts the curve into that Bézier, and a press beside them edits nothing. A press on an elastic, bounce or steps curve converts it into a point curve and drags the nearest point.

Curve kinds

Spring and custom points
Default density Compact density Restart from the initial state Show the copyable source

Springs show Sliders for mass, stiffness and damping, and their progression is normalized to their own settle time. Custom curves are CSS linear() point lists: press the empty graph to add a point, drag points along the time axis between their neighbours, and press Delete on a focused point to remove it. Springs and parametric families have no CSS notation, so the CSS export samples them into linear().

KindNotationParametersbeziercubic-bezier(x1, y1, x2, y2) (CSS)x in 0–1; y free on input, edited within −1 to 2springspring(mass stiffness damping), optional velocity (not CSS)mass 0.1–10, stiffness 1–1000, damping 0–100, velocity −10 to 10parametricsine(out), power(in-out 3), back(out 1.702), … (not CSS)family, direction (in, out, in-out, out-in) and family valuesparametricsteps(4, jump-end) (CSS)count 1–50; the position follows the directioncustomlinear(0, 0.3 20%, 1.15 60%, 1) (CSS)ordered points, inputs 0–1, outputs within −1 to 2

The widget accepts every notation above plus the CSS keywords linear, ease, ease-in, ease-out, ease-in-out, step-start and step-end, which parse to their records. Whatever an owner supplies, value reads back as the canonical text: lowercase, numbers with at most three decimals, , between CSS arguments and single spaces inside the non-CSS functions. A spring’s progression is normalized to its own settle time (shown under its controls, capped at 10 s); the exported and previewed time is the duration lane.

A press inside a handle keeps the pointer’s offset from its center, so the handle moves with the pointer instead of jumping under it; a press elsewhere never moves a Bézier handle. Escape or pointer cancellation restores the pre-drag value without a commit; a lost capture settles at the last position.

Points stay between their neighbours on the time axis; the first and last points keep 0% and 100%. Delete or Backspace on a focused point removes it, and the Add point and Remove point Buttons do the same from the keyboard: Add inserts a point halfway between the focused point and the next, Remove drops the focused inner point.

Spring rows (mass, stiffness, damping, velocity) and parametric rows (power exponent, back overshoot, elastic amplitude and period, steps count) show a Label, a Slider and a value field under the NumberField behavior; the Bézier control points have no rows: they are edited on the graph and in the expression field. The family and direction themselves come from the presets. Slider drags propose once per frame and commit on release; Escape during a Slider drag restores the pre-drag value, as the graph does.

The curve model is exported from @tweakpad/ui/widgets: parseEasingCurve, serializeEasingCurve, easingFunction, sampleEasing, toCssEasing, convertEasingCurve, springSettleTime and the record types.

Layouts and sizes

layout="extended" (the default) keeps the presets in a column beside the graph. layout="compact" moves them behind a Toggle in the header, where they overlay the graph; it fills its container up to 32rem, which suits a Dialog or a Popover. size (sm, default, lg) scales the graph, handles and thumbnails together. layout="compact" hides them behind an icon-only presets Toggle (named “Show presets”) in the header: they overlay the graph on a translucent surface and scroll inside it, a pick closes them, Escape closes them and returns focus to the Toggle, and presetsOpen / defaultPresetsOpen form a lane with tp-open-change.

Compact in a dialog
Default density Compact density Restart from the initial state Show the copyable source
Edit transition

Duration and preview

duration is the time in seconds that the preview, the frames bar and the exports use; duration-min, duration-max and duration-step set the range of its Slider. timing="false" hides the row and drops the time from the CSS export. preview picks what animates behind the graph: scale (the default), scale-x, scale-y, rotation or none.

Rotation preview
Default density Compact density Restart from the initial state Show the copyable source

The frames bar under the graph draws 40 ticks at the eased positions, so they bunch where the motion is slow. frames="false" hides it, preview-controls="false" hides the play and preview mode Buttons, and preset-picker="false" removes the presets for a curve that should only be tuned by hand.

With previewControls (default true) a Button in the header shows the current mode and cycles to the next one on each press, replaying the preview. The preview replays on every settled change (a release, a key, a field commit, a preset, a conversion), on every settled duration change (a Slider release or key, a field commit, a time typed in the expression field) and on the play Button, running for the duration with the CSS easing through the motion role preview. Under reduced motion it completes without motion and the frames bar remains the timing reference.

Presets

Presets are a property, set from data. Each group becomes a section of tiles with a thumbnail of the curve; the tile whose value matches the current curve stays pressed. The preset picker lists the built-in table (Basic, Spring, and one group per easing family with its four directions) or the groups supplied through presets. Pressing a tile proposes and commits with item-press (keyboard from the keyboard) and presetId in the metadata, and restarts the preview from the start, also when the tile is already the current one.

Design system presets
Default density Compact density Restart from the initial state Show the copyable source

Export adapters

The footer shows the curve as text through the active adapter, CSS by default, with a Copy button. The field is editable: type any notation the editor accepts, with an optional leading time such as 500ms, and press Enter. adapters replaces the list of serializers; each is { id, label, format(curve, context) }, where context carries the duration, the CSS timing function and a samples(count) function. The field itself is the expression editor: its numbers are highlighted in the accent color; typing commits on Enter or blur (any notation the value model accepts, with an optional leading time such as 500ms that sets the duration) and Escape reverts; Up and Down step the number at the caret by 0.01 (Shift, Control or Meta 0.1, Alt 0.001; milliseconds by 10) and commit at once, keeping the number selected; text that is not an easing is flagged and reverts on blur. The CSS adapter emits cubic-bezier(), steps() and linear() verbatim, samples springs and parametric families into linear() (at most 100 points, three decimals), and prefixes the duration (700ms …) when the time control is on.

Export for code
Default density Compact density Restart from the initial state Show the copyable source

default-adapter picks the starting adapter and output reads the current text.

Listening to changes

Every edit is a cancelable tp-value-change, and each settled interaction (a handle release, a key, a preset, a field commit) sends one tp-value-commit. Do expensive work, such as saving or regenerating styles, on the commit. event.detail.metadata.curve holds the parsed record, so a handler can refuse a curve by its parameters. Events of the composed Sliders, Selects, Tabs, Toggle groups, Inputs and the Copy button never leave the widget’s shadow root; a rejected widget proposal reverts the nested control.

Commit and veto
Default density Compact density Restart from the initial state Show the copyable source
Saved: cubic-bezier(0.42, 0, 0.58, 1)

Controlled

Supply value and duration to own the curve and the time. Every edit then arrives as a proposal, tp-value-change for the curve and tp-duration-change for the time, and the editor shows only what you write back. Here the accepted values feed a transition in your own styles. Supply value for a controlled editor or defaultValue for an uncontrolled one; the mode stays fixed for the element’s lifetime. The same rule applies to the duration, adapter and presetsOpen lanes.

Controlled owner
Default density Compact density Restart from the initial state Show the copyable source
transition: translate 0.5s cubic-bezier(0.32, 0.72, 0, 1);

Springs and parametric families have no CSS notation; for those, read output with the CSS adapter, which samples them into linear().

In a form

The editor is a form control: the canonical string submits under name, required validates, and a form reset restores default-value. Inside a Field the label focuses the first graph handle.

Field, small size
Default density Compact density Restart from the initial state Show the copyable source

readonly keeps every handle and field focusable but ignores edits; disabled disables every nested control and withholds the value from the form.

Keyboard

Every handle and point has a Time and a Progression range input: arrows step by 0.01, Shift, Control or Meta with an arrow and Page Up or Page Down by 0.1, Alt with an arrow by 0.001, Home and End reach the bounds of the focused axis (0–1 for time, −1 to 2 for progression). The time axis does not mirror in right-to-left writing. Keyboard changes commit immediately. The Tabs, Toggle groups, Selects, Sliders and fields keep their own keyboard contracts.

Constituents and accessibility

The root is a group named by label (default “Easing”). Handle and point inputs are sliders named Label: Control point 1 Time or Label: Point 2 Progression with three-decimal value text; the graph is a group, the frames bar an image, the preview shape hidden. Presets are toggles named by the preset, one roving stop per group. Focus order: header (Tabs, play, preview mode Button, presets Toggle), presets in the extended layout, graph inputs, controls, duration, footer (adapter, output, Copy). strings overrides every visible and accessible name; locale formats the fields.

API Reference

Every element also accepts motion-policy.

Attributes

AttributePropertyTypeDefaultDescriptiondefault-valuedefaultValuestring | undefined—Starting curve of an uncontrolled editor, in any easing notation the editor reads; form reset returns to it.durationdurationnumber—Duration in seconds within the duration range.default-durationdefaultDurationnumber | undefined—Starting duration in seconds of an uncontrolled duration.duration-mindurationMinnumber0Shortest duration in seconds the duration row allows.duration-maxdurationMaxnumber3Longest duration in seconds the duration row allows.duration-stepdurationStepnumber0.01Step in seconds of the duration Slider and field.timingtimingbooleantrueShows the duration row; turned off, the exported text carries no time.adapteradapterstring—Id of the active export adapter, whose text the footer shows and copies; setting it makes the choice controlled.default-adapterdefaultAdapterstring | undefined—Starting export adapter of an uncontrolled adapter choice, by id.preset-pickerpresetPickerbooleantrueShows the preset picker.layoutlayoutEasingEditorLayout'extended'Places the presets in a column beside the graph, or behind a header Toggle over the graph for tight spaces such as a Dialog.presets-openpresetsOpenboolean—Presence of the presets overlay in the compact layout.default-presets-opendefaultPresetsOpenbooleanfalseOpens the presets overlay of the compact layout on first render.sizesizeEasingEditorSize'default'Scales the graph, the handles, the thumbnails and the presets column together.previewpreviewEasingPreviewMode'scale'How the shape behind the graph replays the motion: growing, stretching on one axis, turning, or not at all.preview-controlspreviewControlsbooleantrueShows the header Button that cycles through the preview modes.framesframesbooleantrueShows the bar of ticks under the graph that marks where the frames fall.labellabelstring''Accessible name of the editor's group and the prefix of every handle's name; a surrounding Field supplies it otherwise.localelocalestring''Locale used to format the numbers in the fields.

Properties

PropertyTypeDefaultDescriptionadaptersreadonly EasingAdapter[][cssEasingAdapter]The export formats offered in the footer, each turning the curve into text; CSS alone by default.presetsreadonly EasingPresetGroup[][]Groups of named curves for the preset picker, replacing the built-in table.stringsPartial<EasingEditorStrings>{}Replaces built-in names and messages (tabs, preset names, control names) for localization.onValueChangeEasingEditorValueChangeCallback | undefined—Called synchronously with each tp-value-change proposal, before listeners; a controlled owner writes value back here to accept it.onValueCommittedEasingEditorValueCommitCallback | undefined—Called with each settled value, once per drag, keystroke, preset or field commit.curveread-onlyEasingCurve | null—The parsed record, or null when empty.kindread-onlyEasingKind | null—The kind of the current curve (Bézier, spring, parametric or custom points), read from the value; null when empty.categoryread-onlyEasingCategory | null—spring for springs, ease for every other kind; null when empty.easingFunctionread-only(progress: number) => number—The progression function of the current record (identity when empty).outputread-onlystring—The active adapter's text for the current record.presetIdread-onlystring | null—The id of the preset whose canonical value equals the value, or null.controlledread-onlyboolean—Whether the value is owned by the application through value.draggingread-onlyboolean—Whether a handle, point or Slider drag is in progress.playingread-onlyboolean—Whether the preview is replaying the motion.

Events

EventTypeDescriptiontp-value-changeTpValueChangeEvent<string>Cancelable curve proposal; detail.metadata carries the parsed curve and, when they apply, the edited surface, parameter, handle, point and presetId.tp-value-commitTpValueCommitEvent<string>A settled curve: once per drag on release, and at once for keys, presets and field commits.tp-duration-changeTpValueChangeEvent<number>Cancelable duration proposal, in seconds.tp-adapter-changeTpValueChangeEvent<string>Cancelable proposal of another export adapter.tp-open-changeTpValueChangeEvent<boolean>Cancelable proposal to show or hide the presets overlay of the compact layout.tp-field-valueCustomEvent<{ value: string; previousValue: string; reason: ChangeReason; }>Each committed value, for a surrounding Field's validation state.

Methods

MethodReturnsDescriptionsetValue(value, reason, sourceEvent?)booleanProposes and commits an easing; returns whether the proposal was accepted.play()Promise<void>Replays the preview; resolves when it finishes.setPresetsOpen(open, reason, event?)booleanOpens or closes the presets overlay (compact layout); returns whether it was accepted.

Slots

SlotDescriptionfooterContent at the bottom of the editor, such as an action row.

CSS parts

PartDescription::part(easing-editor)The root group named by the label, holding the header, the graph, the controls and the export row.::part(easing-editor-adapter)The Select of export adapters.::part(easing-editor-badge)The Badge over the graph that names the matching preset or reads Custom.::part(easing-editor-controls)The rows under the graph that edit the curve's parameters or points.::part(easing-editor-copy)The Copy button for the exported text.::part(easing-editor-duration)The duration row: a Slider and a field in seconds.::part(easing-editor-field)A numeric value field beside a Slider.::part(easing-editor-footer)The wrapper of the footer slot at the bottom of the editor.::part(easing-editor-header)The top row: the category Tabs, the play and preview mode Buttons and, in the compact layout, the presets Toggle.::part(easing-editor-label)The visible label above the editor.::part(easing-editor-output-group)The export row: the adapter name, the editable expression and the Copy button.::part(easing-editor-parameter)One parameter row: a Label, a Slider and a value field.::part(easing-editor-play)The Button that replays the preview.::part(easing-editor-preset-grid)The Toggle group that lays out the tiles of a group.::part(easing-editor-preset-item)One preset tile.::part(easing-editor-presets)The preset picker: a column beside the graph, or an overlay in the compact layout.::part(easing-editor-presets-toggle)The Toggle that shows the presets over the graph in the compact layout.::part(easing-editor-preview-mode)The Button that cycles through the preview modes.::part(easing-editor-slider-range)A parameter Slider's filled range.::part(easing-editor-slider-thumb)A parameter Slider's thumb.::part(easing-editor-slider-track)A parameter Slider's track.

Components, widgets and patterns that use this one.