A self-contained, floating on-page visual editor that lets you select, style, resize, reposition, and insert DOM elements on any web page — live, without touching code. Changes are tracked in a full undo/redo history and can be exported as copy-ready CSS and HTML for use in any builder or codebase.
- Demo
- Features
- Versions
- Quick Start
- How It Works
- Pane Reference
- Keyboard Shortcuts
- API Reference
- Configuration
- Contributing
- License
Open EditorToolbar.html directly in any modern browser — no build step, no server, no dependencies.
| Feature | Description |
|---|---|
| Element Selection | Click the cursor icon to enter pick mode; click any element to select it |
| Multi-Select | Hold Shift and click to select multiple elements simultaneously |
| Bounding Box | Live-tracking selection frame with 8 resize handles (corners + edges) |
| Free Reposition | Drag the move handle to reposition elements; constrained within their parent |
| Snap to Siblings | Pink alignment guides snap the dragged element to parent/sibling edges and centers |
| Typography | Google Fonts search, font size, weight (100–900), color, style, alignment, line height |
| Layout | Width/height with px/% toggle, aspect-ratio lock, min/max constraints, nudge, opacity, display |
| Style | Background color, border radius, border/outline presets, cursor |
| Effects | Box shadow, transforms, full CSS filter rack (blur/brightness/contrast/saturation/hue/grayscale), transitions |
| Spacing | Padding and margin as T/R/B/L number inputs with linked-sides toggle, border radius per corner, gap |
| Insert | Add paragraphs, headings, divs, cards, sections, buttons, images, spans with preset styles |
| Wrap | Multi-select 2+ elements and wrap them in a new container (div/section/article/etc.) |
| Undo / Redo | Full history for every style change, resize, reposition, and element insertion |
| Accept & Export | Review all changes in a summary popup; copy CSS + HTML to clipboard |
| True Reset | Restores every element to its exact page-load inline style, removes all inserted elements |
| File | Use Case |
|---|---|
EditorToolbar.html |
Drop into any plain HTML project; zero dependencies |
EditorToolbar.tsx |
React component for Lovable, Bolt, or any React/Tailwind project |
Both versions share identical behavior. The HTML file is the source of truth; the TSX file mirrors it.
-
Copy the three sections marked in
EditorToolbar.htmlinto your project:- The
<style>block → into your CSS - The
#et-bbox,#et-snap-v,#et-snap-h,#et-popup-overlay,#et-rootmarkup → into your HTML body - The
<script>block → before your closing</body>tag
- The
-
Add
data-component="Your Name"to any element you want the toolbar to recognise:
<section data-component="Hero Section">
<h1>Welcome</h1>
<p>Subtitle text here.</p>
</section>
<div class="card-grid" data-component="Feature Cards">
<!-- cards -->
</div>- Open the page in a browser. The toolbar appears at the bottom center.
Elements without
data-componentcan still be selected using the cursor pick mode —data-componentjust gives them a friendly display name in the toolbar.
-
Copy
EditorToolbar.tsxintosrc/components/EditorToolbar.tsx -
Import and render it at the root level of any page:
import { EditorToolbar } from '@/components/EditorToolbar';
export default function MyPage() {
return (
<>
<main>
{/* your page content */}
</main>
<EditorToolbar />
</>
);
}- Add
data-componentattributes to elements you want selectable:
<section data-component="Hero Section">
<h1>Welcome</h1>
</section>The toolbar is a floating pill pinned to the bottom center of the viewport. All logic lives inside a single IIFE (const ET = (() => { ... })()) — no global pollution, no external dependencies, no build step required for the HTML version.
Clicking the cursor icon enters pick mode. Hovering any element outlines it; clicking selects it. Holding Shift while clicking adds or removes elements from a multi-selection pool. Selected elements are tracked by a requestAnimationFrame loop that keeps the bounding box accurate through scroll, resize, and layout changes.
Every UI control routes through a central apply(property, value) function. This writes the style directly to the element's inline style attribute and pushes an entry onto the history stack: { el, prop, old, val, elName }. Element insertions push a different entry type: { type: 'insert', el, parent, before, pos, elName }.
- Undo reads from the top of the history stack and reverses the entry — setting the old CSS value or removing the inserted element from the DOM.
- Redo re-applies entries forward through the stack.
- Accept freezes the current pending batch, clears the working stack, and opens the export popup.
The Accept popup shows every change grouped by element with deduplication (only the final value per property is shown). Copy CSS generates a clean, comment-annotated output:
/* ── Editor Toolbar — Accepted Changes ── */
/* ── Inserted Elements (HTML) ── */
/* Target: "Hero Section" | Position: inside */
<h2 data-component="New Heading" ...>My Heading</h2>
/* ── Style Changes (CSS) ── */
/* Hero Section <section> */
font-family: "Poppins", sans-serif;
font-size: 48px;
color: #111111;| Control | What It Does |
|---|---|
| Google Fonts search | Type a font name, click a result — fetched from Google Fonts and applied instantly |
| Font size | Slider, 10–96px |
| Font weight | Buttons for every weight from 100 to 900 |
| Text color | 20-swatch palette + native OS color picker + hex/rgb input field |
| Style | Italic, Normal, Underline, No decoration, UPPERCASE, None |
| Align | Left, Center, Right, Justify |
| Line height | Slider, 1.0–3.0 |
| Control | What It Does |
|---|---|
| W / H inputs | Set exact width and height in px or % |
| Unit toggle | Switch between px and % for W/H inputs |
| Aspect ratio lock | Lock W:H ratio — changing one value adjusts the other; also active while Shift-dragging a resize handle |
| Auto / Full W | Reset to auto or set width to 100% |
| Min/Max W and H | Set min-width, max-width, min-height, max-height |
| Nudge | Arrow buttons to move the element 1/2/4/8/16px at a time |
| Step size | Choose the nudge amount |
| Position | static, relative, absolute, fixed, sticky |
| Opacity | Slider, 0–100% |
| Display | block, flex, grid, inline-block, inline, none |
| Overflow | visible, hidden, auto, scroll |
| Control | What It Does |
|---|---|
| Background color | Same swatch + color picker + hex input as text color |
| Border radius | Slider, 0–64px |
| Border | Preset styles: None, Light, Dark, Blue, Lime, Dashed, Double |
| Outline | Preset styles: None, Blue, Lime, Dashed |
| Cursor | default, pointer, text, move, not-allowed, grab |
| Control | What It Does |
|---|---|
| Box Shadow | 9 presets: None, Soft, Medium, Deep, Deeper, Ring, Lime Ring, Brutal, Inset |
| Transform | 10 presets: Reset, Scale+, Scale−, Scale++, Rot 2°, Rot −2°, Rot 45°, SkewX, SkewY, Float |
| CSS Filters | Live sliders for: Blur (0–20px), Brightness (0–200%), Contrast (0–200%), Saturate (0–200%), Hue (0–360°), Grayscale (0–100%) |
| Transition | None, Fast (0.2s), Medium (0.4s), Slow (0.6s), Bounce (cubic-bezier) |
| Control | What It Does |
|---|---|
| Padding T/R/B/L | Four number inputs; linked toggle syncs all four sides |
| Margin T/R/B/L | Four number inputs; linked toggle syncs all four sides |
| Border radius | All corners + individual TL/TR/BL/BR inputs |
| Border width + style | Width in px; style chips: Solid, Dashed, Dotted, None |
| Gap | All, Column, Row |
| Width / Height | Exact pixel size |
| Control | What It Does |
|---|---|
| Target picker | Click to activate; click any element on the page to set it as the insertion target |
| Position | Inside (append child), Before (insert sibling), After (insert sibling) |
| Element types | Paragraph, Heading (H2), H1, Div, Card, Section, Button, Image placeholder, Span |
| Content | Text content for the new element |
| Component name | The data-component label assigned to the inserted element |
| CSS classes | Optional class names to add |
| Preset styles | None, Card, Pill, Hero, Badge, Divider |
| Wrap (multi-select) | Appears when 2+ elements are selected — choose a wrapper tag, name it, and click Wrap |
| Shortcut | Action |
|---|---|
P |
Toggle pick mode |
Shift + Click |
Add / remove element from multi-selection |
Arrow keys |
Nudge selected element (uses the active step size) |
Ctrl / Cmd + Z |
Undo |
Ctrl / Cmd + Y or Ctrl / Cmd + Shift + Z |
Redo |
Escape |
Close active pane or exit pick mode |
Shift (hold while resizing) |
Lock aspect ratio during corner/edge drag |
All toolbar logic is exposed on the global ET object. These are the methods you can call from outside the toolbar if you need to integrate it programmatically.
ET.togglePick() // Enter / exit element pick mode
ET.togglePane('text') // Open or close a pane: 'text'|'layout'|'style'|'effects'|'spacing'|'insert'
ET.undo() // Undo the last change
ET.redo() // Redo the last undone change
ET.accept() // Freeze pending changes and open the export popup
ET.erase() // Clear all inline styles on the selected element
ET.resetAll() // Restore every element to its page-load state
ET.closePopup() // Close the export popup
ET.copyChanges() // Copy the CSS/HTML summary to clipboardET._a('fontSize', '24px') // Apply any CSS property to the selected element
ET._gb(btnEl, 'textAlign', 'center') // Apply + mark button active in its groupET._rdStart(mouseEvent, 'br') // Begin a resize drag from a handle: 'tl'|'tr'|'bl'|'br'|'tm'|'bm'|'ml'|'mr'
ET._szApply() // Apply W/H from the layout pane inputs
ET._szApply('100%', null) // Apply explicit values
ET._setResizeUnit('px') // Switch resize unit: 'px' or '%'
ET._toggleLock() // Toggle aspect-ratio lockET._pi('top', '16') // Set padding for one side (or all sides if linked)
ET._mi('left', '8') // Set margin for one side (or all sides if linked)
ET._tl('pad') // Toggle padding link: 'pad' or 'mar'
ET._bs('dashed') // Set border style chip active: 'solid'|'dashed'|'dotted'|'none'ET._itp() // Activate insert target pick mode
ET._ipos('inside') // Set insert position: 'inside'|'before'|'after'
ET._ityp('card') // Set element type to insert
ET._ips(el, 'card') // Set a preset style chip active
ET._ins() // Execute the insertion
ET._wrap() // Wrap all currently multi-selected elements
ET._swt('section') // Set the wrap tagET._sw(swatchEl, 'bg') // Click a swatch: 'bg' (background) or 'fg' (text color)
ET._np(inputEl, 'fg') // Native color picker input changed
ET._ah('bg') // Apply the hex/rgb value typed into the inputET._nu('up') // Nudge selected element: 'up'|'down'|'left'|'right'
ET._sn(4) // Set nudge step size in pxET._sf('blur', '4px') // Apply a CSS filter function (composes with existing filters)All of the following are single-line changes at the top of the IIFE inside the <script> block.
const ACCENT = '#0099fe';Change this one value to retheme every highlight, slider thumb, focus ring, active button, and bounding box in the toolbar.
const SNAP_THRESH = 6;The pixel distance within which a dragged element snaps to a sibling or parent edge. Increase for easier snapping, decrease for more precision.
Add data-component="Name" to any HTML element. Elements without this attribute are still selectable via pick mode, but won't have a friendly name in the toolbar.
All elements inserted via the Insert pane receive data-et-inserted="1" automatically. The Reset All function uses this attribute to find and remove them.
Contributions welcome. Before submitting a PR, please run the validation check described in CONTRIBUTING.md — specifically the syntax and public API coverage tests — since this project has had real regressions caused by missing closing braces and nested template literals that are easy to introduce accidentally.
See CONTRIBUTING.md for the full development guide.
MIT — see LICENSE for details.
