svelte-gradient-editor
v0.5.0
Published
Standalone Svelte 5 gradient editor component with a typed model API.
Readme
Svelte Gradient Editor
Standalone Svelte 5 gradient editor component with a typed model API..
Like it? I'd appreciate the support :)
Description
The best Gradient Editor you'll find for Svelte. We use this package on our project chatstyle.gg, go check it out! Everything below this line isn't handwritten and optimized for da clankers, have fun!
It keeps two representations of the same gradient synchronized:
- A CSS gradient string for direct use in
background-image - A structured
GradientModelobject for application logic
Install
npm install svelte-gradient-editorPeer dependency:
svelte ^5.0.0
Runtime dependencies installed with the package:
svelte-awesome-color-pickerprovides the stop color picker UI.colordprovides color parsing, validation, conversion, alpha handling, and color math.
Quick Start
<script lang="ts">
/* Import that shit */
import { GradientEditor } from 'svelte-gradient-editor';
import type { GradientEditorMessages, GradientModel } from 'svelte-gradient-editor';
/* THIS is the gradient result. You'll get the full css gradient value. */
let value = 'linear-gradient(90deg, #ff0000 0%, #0000ff 100%)';
let model: GradientModel | undefined;
/* Optional: pass translated strings to the editor */
const messages: GradientEditorMessages = {
gradientMode: 'Gradient type',
linear: 'Linear',
conic: 'Conic',
radial: 'Radial',
angle: 'Angle',
stopAriaLabel: ({ position, index, total }) =>
`Color stop ${index + 1} of ${total} at ${position}%`
};
</script>
<GradientEditor
bind:value
bind:model
/* If needed, you can adjust the decimal accuracy */
angleDecimals={0}
positionDecimals={1}
/* See translated messages above */
{messages}
on:change={(event) => {
console.log(event.detail.value);
console.log(event.detail.model);
}}
/>What The Component Does
- Supports editing
linear-gradient(...),conic-gradient(...), andradial-gradient(...) - Keeps
valueandmodelsynchronized after every change - Lets users add, move, recolor, and delete gradient stops
- Uses
svelte-awesome-color-pickerfor stop color editing with hex, rgb, hsv, and alpha controls - Lets users switch between linear, conic, and radial gradients
- Lets users edit linear/conic angle and radial shape
- Uses the same stop editor abstraction for all supported gradient modes
- Emits normalized output rather than preserving unsupported input syntax verbatim
Public Exports
The package entry point exports:
import {
GradientEditor,
cloneGradientModel,
defaultGradientModel,
modelSignature,
normalizeColor,
normalizeModel,
parseGradient,
serializeGradient
} from 'svelte-gradient-editor';
import type {
ConicGradientModel,
GradientEditorMessages,
GradientModel,
GradientStop,
LinearGradientModel,
RadialGradientModel
} from 'svelte-gradient-editor';Component API
Props
| Prop | Type | Default | Purpose |
| --- | --- | --- | --- |
| value | string | '' | Bound CSS gradient string. The component normalizes it to a supported canonical form. |
| model | GradientModel \| undefined | undefined | Bound typed model. If provided, this takes precedence over value. |
| showValue | boolean | false | Shows the current serialized gradient string inside the component UI. |
| angleDecimals | number | 0 | Decimal precision for linear angle editing and emitted angle values. |
| positionDecimals | number | 0 | Decimal precision for stop position editing, dragging, adding, and emitted values. |
| messages | GradientEditorMessages | {} | Overrides built-in English labels and ARIA text. |
Event
change
type GradientEditorChangeDetail = {
value: string;
model: GradientModel;
};The component emits change on every committed editor update.
Bindings
bind:valueCanonical CSS gradient string suitable forbackground-imagebind:modelCanonical structured model synchronized withvalue
Types
type GradientStop = {
id: string;
color: string;
position: number;
};
type LinearGradientModel = {
type: 'linear';
angle: number;
stops: GradientStop[];
};
type ConicGradientModel = {
type: 'conic';
angle: number;
stops: GradientStop[];
};
type RadialGradientModel = {
type: 'radial';
shape: 'circle' | 'ellipse';
stops: GradientStop[];
};
type GradientModel = LinearGradientModel | ConicGradientModel | RadialGradientModel;type GradientEditorMessages = {
gradientMode?: string;
linear?: string;
conic?: string;
radial?: string;
radialShape?: string;
ellipse?: string;
circle?: string;
angleDial?: string;
angle?: string;
gradientTrack?: string;
gradientStops?: string;
editGradientStop?: string;
stopColor?: string;
position?: string;
closeColorPicker?: string;
deleteStop?: string;
stopAriaLabel?: (input: {
position: number;
index: number;
total: number;
color: string;
}) => string;
};Behavior Contract
This section is intended to be explicit enough for both humans and coding agents.
Source Of Truth
- If
modelis passed, the component usesmodelas the incoming source of truth. - If
modelis not passed, the component parsesvalue. - Internally, the component normalizes the gradient before writing back to
valueormodel.
Normalization
- Opaque colors are normalized to lowercase 6-digit hex.
- Alpha colors are normalized to deterministic
rgba(r, g, b, a)syntax. - Multiline gradient strings and repeated CSS whitespace are accepted and normalized to single-line output.
- Stop positions are clamped to
0..100. - Stops are sorted by position.
- At least 2 stops always exist after normalization.
- Missing stop positions are distributed automatically.
- Linear and conic angles are normalized to a
0..360degree representation. - Radial shape is normalized to either
'circle'or'ellipse'.
Precision
By default, the component edits and emits integers.
<GradientEditor bind:value bind:model />Enable decimals explicitly:
<GradientEditor bind:value bind:model angleDecimals={1} positionDecimals={2} />Rules:
angleDecimalsandpositionDecimalsshould be non-negative integers.- Invalid, negative, or non-finite values fall back to
0. - Precision affects component interactions and emitted values.
- Helper functions like
parseGradient()andserializeGradient()keep their own API and are not driven by component prop precision.
Supported Input
Supported gradient families:
linear-gradient(...)conic-gradient(...)radial-gradient(...)
Gradient input may span multiple lines. Whitespace is collapsed before parsing, and serialized output is always a canonical single-line CSS gradient string.
Supported linear direction syntax:
- Degree syntax such as
90deg - Named directions such as
to right,to top left
Supported radial syntax:
- Shapes
circleandellipse
Supported conic syntax:
conic-gradient(#ff0000 0%, #0000ff 100%)conic-gradient(from 45deg, #ff0000 0%, #0000ff 100%)
Supported stop color syntax:
- 3-, 4-, 6-, and 8-digit hex colors such as
#f00,#f008,#ff0000, and#ff000080 rgb(...)andrgba(...)colors supported bycolord
The stop color picker exposes hex, rgb, hsv, and alpha editing. HSL is not an editor mode.
Unsupported CSS Features
The package does not preserve full CSS gradient syntax.
Examples of unsupported or only partially supported input:
- Linear angle units other than degrees, such as
turn - Conic positioning such as
at center - Radial positioning such as
at center - Radial sizing keywords such as
closest-sideorcover - Named colors such as
redortransparent - HSL and other unsupported CSS color syntaxes
Conic support in v1 is intentionally angle-only. Parsed conic gradients round-trip canonically as conic-gradient(from Xdeg, ...).
When unsupported input is encountered, the parser falls back to the closest supported model or to a safe default gradient.
Color Output Examples
serializeGradient({
type: 'linear',
angle: 90,
stops: [
{ id: 'a', color: '#f00', position: 0 },
{ id: 'b', color: '#0000ff80', position: 100 }
]
});
// linear-gradient(90deg, #ff0000 0%, rgba(0, 0, 255, 0.5) 100%)
serializeGradient({
type: 'linear',
angle: 45,
stops: [
{ id: 'a', color: 'rgb(1, 2, 3)', position: 0 },
{ id: 'b', color: 'rgba(10, 20, 30, 0.25)', position: 100 }
]
});
// linear-gradient(45deg, #010203 0%, rgba(10, 20, 30, 0.25) 100%)Picker edits are emitted through the same canonical model and CSS string. For example, setting a stop to 50% alpha in the picker serializes that stop as rgba(...).
User Interactions
The editor supports these built-in interactions:
- Click the gradient track to add a stop
- Drag a stop handle to move it
- Click a stop to open its popup editor
- Edit stop color with the color picker
- Edit stop position with the numeric input
- Close the stop popup with its close icon or
Escape - Delete the selected stop from the popup
- Press
DeleteorBackspaceoutside editable fields to remove the selected stop - Switch between linear and radial modes
- Switch between linear, conic, and radial modes
- Drag the angle dial or use keyboard input on it
Guardrails:
- A gradient cannot be reduced below 2 stops
- Deletion is disabled when only 2 stops remain
Helper Functions
parseGradient(value)
Parses a supported CSS gradient string into a normalized GradientModel.
const model = parseGradient('linear-gradient(to right, #111111 0%, #eeeeee 100%)');serializeGradient(model)
Serializes a GradientModel into canonical CSS.
const css = serializeGradient({
type: 'conic',
angle: 45,
stops: [
{ id: 'a', color: '#ffaa00', position: 0 },
{ id: 'b', color: '#220044', position: 100 }
]
});normalizeModel(model)
Returns a normalized clone of a model:
- stop positions clamped and sorted
- colors normalized
- invalid radial shapes corrected
- conic output canonicalized to
from <angle>deg - angle rounded to the helper's built-in precision
defaultGradientModel(overrides?)
Creates a valid default model.
Defaults:
- linear gradients default to angle
90 - conic gradients default to angle
0 - radial gradients default to shape
'ellipse' - default stops are
#ff7a18 0%and#5b4dff 100%
cloneGradientModel(model)
Returns a normalized deep clone of the gradient model.
modelSignature(model)
Returns a JSON string signature of the normalized model. Useful for stable equality checks after normalization.
normalizeColor(value)
Accepts supported hex and rgb/rgba values and returns canonical CSS, or null for unsupported input.
normalizeColor('#abc'); // '#aabbcc'
normalizeColor('#fff0'); // 'rgba(255, 255, 255, 0)'
normalizeColor('#abcd'); // 'rgba(170, 187, 204, 0.87)'
normalizeColor('rgba(1, 2, 3, 0.25)'); // 'rgba(1, 2, 3, 0.25)'
normalizeColor('red'); // nullSecurity Notes
Normalize untrusted gradient strings or models before rendering them. Do not render arbitrary user-provided gradient CSS directly outside the component.
The component only writes normalized gradient values into inline styles. Unsupported color syntax is intentionally rejected and falls back safely, which reduces CSS injection risk.
Messages And Localization
All user-facing text is consumer-controlled from code. The package does not require any specific i18n library.
<script lang="ts">
import { GradientEditor } from 'svelte-gradient-editor';
import type { GradientEditorMessages } from 'svelte-gradient-editor';
const messages: GradientEditorMessages = {
gradientMode: 'Modo de degradado',
linear: 'Lineal',
conic: 'Conico',
radial: 'Radial',
radialShape: 'Forma radial',
ellipse: 'Elipse',
circle: 'Circulo',
angleDial: 'Control de angulo',
angle: 'Angulo',
gradientTrack: 'Pista de degradado',
gradientStops: 'Paradas del degradado',
editGradientStop: 'Editar parada',
stopColor: 'Color',
position: 'Posicion',
closeColorPicker: 'Cerrar selector de color',
deleteStop: 'Eliminar parada',
stopAriaLabel: ({ position, index, total, color }) =>
`Parada ${index + 1} de ${total}: ${color} en ${position}%`
};
</script>
<GradientEditor bind:value bind:model {messages} />Theming
The component ships with self-contained styles. Override its CSS custom properties on the component or any parent container.
<div
style="
--gradient-editor-text: #e5e7eb;
--gradient-editor-border: rgba(229, 231, 235, 0.2);
--gradient-editor-accent: #60a5fa;
"
>
<GradientEditor bind:value bind:model />
</div>Supported CSS variables:
--gradient-editor-bg--gradient-editor-panel--gradient-editor-border--gradient-editor-text--gradient-editor-muted--gradient-editor-accent--gradient-editor-shadow--gradient-editor-radius
AI Agent Notes
If you are generating code against this package, assume these rules:
GradientEditoris the only component export.valueandmodelare both writable bindings.modelwins overvaluewhen both are provided.- Emitted
change.detailalways contains bothvalueandmodel. - The component only supports linear, conic, and radial gradients.
- Canonical output uses degree-based linear/conic gradients, hex opaque stop colors, and
rgba(...)alpha stop colors. - The stop color picker supports hex, rgb, hsv, and alpha editing. HSL is not exposed as a UI mode.
- Do not rely on unsupported CSS syntax surviving a parse/serialize round-trip.
- Do not assume decimal stop positions unless
positionDecimalsis set. - Do not assume decimal angles unless
angleDecimalsis set. - The package expects Svelte 5.
Local Development
npm install
npm run dev
npm run test
npm run check
npm run buildCommands:
npm run devstarts the local Vite demonpm run testruns the Vitest suitenpm run checkrunssvelte-checknpm run buildbuilds the library package intodist/
