Skip to content

Theming, i18n, accessibility ​

Theme ​

Set theme="dark" | "light" | "auto" and override tokens on the element:

css
sealpixel-editor {
  --sealpixel-color-accent: #d94f8a;
  --sealpixel-radius: 12px;
  --sealpixel-font: 'Inter', system-ui, sans-serif;
  --sealpixel-toolbar-size: 64px;
}
sealpixel-editor::part(nav) { background: transparent; }

Parts: root, nav, stage, panel, toolbar, undo, redo, history, export, download, dimensions, tool, button, viewport, canvas, overlay, status, toast, empty. All of them are reachable from the host page through nested shadow roots, so sealpixel-editor::part(export) { display: none } hides the Export button.

See Customize the look for a live pane that prints the CSS for any combination of these tokens.

While a source loads, the empty part shows the Sealpixel mark with its inner square breathing, in the muted foreground colour; the caption under it is the editor.loading string. The first preview then fades in over the checker instead of cutting in. Both stop under prefers-reduced-motion.

Locale ​

html
<sealpixel-editor locale="de"></sealpixel-editor>

Shipped locales (en, de, fr, es, pt, ja) resolve from the locale attribute; a region suffix such as de-AT falls back to de. Pass your own dictionary to add a language or to change single strings:

ts
import de from '@sealpixel/ui/locales/de';
editor.locale = 'de';
editor.dictionary = { ...de, 'nav.download': 'Speichern' };

Dictionaries are flat { key: 'text with {placeholders}' }. A dictionary is merged over the shipped locale for locale: its keys replace those strings, and every other string stays in the shipped locale. editor.dictionary = { 'nav.download': 'Speichern' } with locale="de" therefore changes one label and keeps the rest German; spreading the shipped locale in, as above, gives the same result. For a language that is not shipped, pass every key. Keys that neither the dictionary nor the shipped locale has fall back to English and warn in development.

Phone layout ​

The editor switches layouts on its own container width, not on the viewport. Below 768 px the toolbar moves to the bottom, the tool panel sits above it and the stage takes the rest. Nothing to configure: a narrow sidebar or a phone both get this layout. Pinch zooms, drag pans, and a single finger moves crop handles, redaction boxes and annotations. The image never leaves the view: panning stops at the edges, a plain scroll only pans while the image is larger than the stage, and a double tap or the zoom badge fits it again. Switching tools keeps the view steady: when a tool shows the uncropped image, the same source pixels stay where they were, at the same size. Tapping an element selects it and shows a trash handle above it, so nothing needs a list in the panel. Safe-area insets are respected, so the toolbar clears a home indicator.

On a small screen every control counts. show-history="buttons" keeps undo and redo but drops the timeline, show-export="never" hides Export (see Download and export).

Live editor in a 340 px frame. Drag, pinch, tap.Device frame: html5-device-mockups (MIT)

Under the hood the host element is a size container (container-type: inline-size) and a ResizeObserver flips the layout. Tool controls always sit in a bar under the stage; above 48rem only the tool rail moves to the left edge. A panel puts its choice of what to work with on the bottom row, marked class="choice", and the settings for that choice above it. Tools receive ctx.layout ('wide' or 'narrow') and may lay out their panel accordingly; the built-in panels compact themselves into the bar, for example finetune shows one dial with a strip of parameter chips under it and crop offers the aspect ratios in a select. Annotate goes further and shows the controls the selected shape actually has: a rectangle gets a stroke width and a fill, a text box gets a font, size, line height and alignment instead.

A number gets the control its range deserves. A value with a neutral in the middle — the finetune parameters, a rotation — gets the dial, where a scale travelling under a fixed needle shows direction and distance from zero. A magnitude that counts up from a floor — a font size, a line width, a corner radius, a margin — gets a stepper, which reads, types and nudges by one in a fifth of the width. Resize binds its two dimensions with a link: closed, typing either one writes both at the source's ratio and the fit mode steps aside, because nothing is being squeezed.

Crop writes its rectangle to the log the moment a handle is released, but the stage keeps showing the whole image, because a corner cannot be pulled outwards through an edge that is off screen. So that releasing does not look like doing nothing, the crop settles: half a second after the pointer rests, the preview is clipped to the crop and the gizmo fades, leaving the picture that will come out with the stage's own backdrop around it. Clipping rather than painting over is what carries the checker through without a seam; a tool asks for it with ctx.clipPreview(shape).

Redact and annotate use the same clip. Their shapes live in image space, so they render the image before crop and resize — but the user still sees the picture they are working on, because the preview is clipped to what the crop keeps. Before, it was the whole source with the removed part dimmed. Touching the frame is adjusting again. Leaving the tool glides the result into place rather than cutting to it — the one tool switch that moves the image, and only when that visit changed the crop. A tool can ask for the same through ctx.animateNextView().

Accessibility ​

Every tool is keyboard-operable: the toolbar uses roving tabindex, a dial is a native range input with aria-valuetext behind its scale and a stepper a native number field, arrow keys nudge — on a dial by a hundredth of its range (Shift a tenth), on a stepper by one step (Shift ten); a held key on a dial moves the preview and the release writes one update to the edit log, like a drag — status changes announce through an aria-live region, and a control is 44 px under a finger (pointer: coarse) and 32 px under a mouse, both above the 24 px the WCAG 2.2 target-size rule asks for. Every panel passes an axe scan in Chromium and WebKit in the repository's tests.

The elements on the stage are keyboard-operable too. Every redaction region, annotation shape and sticker is a focusable button, labelled by kind and number. Drawing or adding one gives it focus, and so does pressing it with the pointer.

KeyOn a focused region, shape or sticker
Tab, Shift+TabFocus the next or previous element. Focusing selects it.
Enter, SpaceSelect it.
Arrow keysMove it by one screen pixel. A held key moves the preview; the release writes one update to the edit log, like a pointer drag.
Shift+Arrow keysMove it by ten screen pixels.
Delete, BackspaceRemove it. Focus and the selection move to a neighbouring element, or to the editor when none is left.
EscapeDeselect it. While a shape is being drawn, Escape drops the shape.
⌘Z / Ctrl+Z, ⌘⇧Z / Ctrl+YUndo, redo. show-history="none" turns these two off; the others stay on.