Visually customize any website without writing code. Click an element, tweak its colors, font, size, spacing, borders or visibility with a live preview, and SiteEdit remembers your changes for that site and restores them every time you come back.
Chrome Manifest V3 · no dependencies at runtime · no network access · no remote code.
- Element picker: hover highlight with tag label, click to select, Esc to cancel. Page clicks are blocked while picking so you don't trigger links or buttons.
- Live editing of text color, background color, font family, font size, font weight, margin, padding, border width / style / color, border radius, opacity, and hide/show.
- Automatic per-site saving (keyed by hostname) in
chrome.storage.local, restored atdocument_starton every visit, so customized pages don't flash. - Undo / redo (buttons or Ctrl/⌘+Z, Ctrl/⌘+Shift+Z, Ctrl+Y; 100 steps).
- Reset a single element or the whole site (site reset is undoable from the panel).
- JSON export/import for the current site (panel and popup) and for all sites (Manager page, with merge or replace).
- Dynamic pages: rules are real CSS, so elements inserted later match automatically. A debounced
MutationObserverre-attaches the stylesheet if the page replaces it and re-resolves the selected element after re-renders. - Robust selectors: a stable unique
id, then test attributes (data-testid,data-qa,name,aria-label…), then a short>path of stable classes plus:nth-of-type. Hashed and generated class names (CSS-in-JS) and state classes (active,open…) are ignored. - Dark UI: popup, Manager page and an in-page panel isolated in Shadow DOM so site CSS can't break it. You can drag the panel by its header.
Easiest: download SiteEdit.zip from the latest release and unzip it.
- Download and unzip
SiteEdit.zipfrom Releases, or clone this repository. - Open
chrome://extensions, enable Developer mode. - Click Load unpacked and select the project folder (the one containing
manifest.json). - Optional: pin SiteEdit to the toolbar.
- On any
http(s)page click the SiteEdit icon → Open editor on this page (or press Alt+Shift+E). - Click Select element, hover the page and click the element you want to change.
- Edit the fields. Changes preview instantly and are saved when a field is committed (Enter, blur,
or releasing a color or slider). Number-only values get
pxadded; ↑/↓ nudge numbers (Shift = ×10). - Customized elements lists every rule on the site. Click one to select it (this also works for hidden elements); × resets it.
- Close the panel with ✕ or by pressing Esc twice. Your styles stay applied.
- Manage all sites in the popup opens the Manager page: list, delete, export all, import.
| Permission | Why |
|---|---|
activeTab |
Inject the editor only into the tab where you clicked the button or pressed the shortcut. |
scripting |
Perform that on-demand injection. |
storage |
Save customizations locally. |
Content script on http://*/*, https://*/* |
Required for auto-restore. Saved styles have to be reapplied when you revisit a site, before you interact with it. activeTab only applies after a user gesture, so it can't restore on page load. |
The always-on content script is deliberately tiny (core.js + restore.js). It reads only this
hostname's key from storage, applies a constructed stylesheet, and observes the DOM only when the
site has rules. It never reads page content, sends messages, or makes network requests. There are
no host_permissions and no tabs permission, so the extension cannot read your tabs or run
scripts in pages programmatically. The editor itself (editor.js) is injected only on demand
through activeTab.
- No
eval,new Function, remote scripts, CDNs, or network requests. Extension pages use a strict CSP (default-src 'none'; script-src 'self'; connect-src 'none' …). - CSS is generated only from a whitelist of properties. Every value is length-limited, checked with
CSS.supports, and rejected if it contains; { } < > \ ! /*,url(,image(,image-set,expression,javascript:or@import. "Hide" can only setdisplay: none. - Page-blanking guard:
display: noneand opacity below 0.2 are refused on any selector that could match<html>/<body>(comma lists,*,:root,html/bodyin the last compound, including:is()/:where()/:not()forms). At runtime, rules whose selector matches the livehtmlorbody(e.g. a body class) are also skipped. The panel is mounted on<html>, so hiding body content never hides the editor. - Selectors are length-limited, may not contain
{ } < > ; @ /*, and must parse withquerySelector. - Imported JSON is size-limited, has to carry
"format": "siteedit", and goes through the same sanitizer. Invalid hostnames, selectors, properties and values are dropped. - Styles are applied through a constructed stylesheet (
adoptedStyleSheets), never viainnerHTML. The panel builds DOM withtextContentonly.
npm install # installs Puppeteer (and its Chrome for Testing build)
npm test # end-to-end tests in new headless Chromium
HEADFUL=1 xvfb-run -a npm test # same, headful under Xvfbtests/run.mjs serves tests/fixtures/index.html (a page that injects list items and a late banner
after load, then wipes document.adoptedStyleSheets to mimic framework hydration). It then loads the
extension and drives the real UI through Shadow DOM. It covers: panel injection, hover highlight and
Esc cancel, click blocking and selector uniqueness, applying every style group, rejection of
malicious values, storage format, undo/redo (buttons and keyboard), hide, dynamic elements,
persistence after reload including stylesheet re-attachment, reset element, reset site plus undo,
export, sanitized file import, the Manager page export/import, the popup, closing the panel, and
zero console errors.
Automation can't produce the user gesture that grants activeTab, so the test harness loads a
temporary copy of the extension whose manifest adds host_permissions: ["http://127.0.0.1/*"].
The shipped manifest is unchanged.
- Rules apply to the top-level document only, not to iframes or elements inside other sites' Shadow DOM.
- Rules are keyed by hostname, so every page on a host shares them (
www.example.comandexample.comare separate). A selector that only matches on one page has no effect elsewhere. - Elements inside lists and tables (li, tr, role=list …) are pinned to their position, so items added later are not styled. If the site inserts items before the picked one or re-sorts the list, the style follows the position, not the item.
- Selectors that fall back to structural paths (
:nth-of-type) can break if the site changes its markup. Elements with no stable id, attributes or classes are the most fragile. - Styles use
!importantbut can still lose to a site's own inline!importantstyles. - Can't run on
chrome://pages, the Chrome Web Store, or other pages browsers protect from extensions. Pages already open when you install need a reload before auto-restore works there. - Only system/web-safe font stacks are offered (no web font downloads, by design).
- Undo history lives in the open panel session and is cleared when the panel closes. Saved styles are not affected.