Etch doesn’t give you a UI for setting the default value of a select prop. The first option in the list is the default, so the only way to control it from the builder is to reorder your options and put the one you want first. That works, but it means your option order is decided by the default instead of by what makes sense to read. If you have a size prop with small, medium, large and you want medium to be the default, you either accept a jumbled list or accept the wrong default.
This script lets you have both. Drop an instance of the component onto any page or template open in Etch, then paste the script into the browser’s DevTools console and run it. Order doesn’t matter here, you can select the instance on the canvas first and then paste, or paste first and the script will wait until you select one. From there, a modal lists every select prop on the component, including nested ones inside group, repeater, and condition props. Pick a prop, pick a value from its options, and the new default is written to the component definition and saved right away. The script then double checks that the change stuck and lets you know either way.

Note: The script won’t run while you’re in Component Edit Mode. Exit the editor first, then run it against an instance on the canvas.
/**
* Set an Etch component's select props default value
*/
(async () => {
const e = window.etch;
if (!e) return console.error('No etch.');
if (e.blocks.isInComponentEditMode()) {
console.error('Exit the Component Editor before running this.');
return;
}
const M = (title, items) => new Promise((resolve) => {
const overlay = document.createElement('div');
overlay.style.cssText = 'position:fixed;inset:0;background:var(--e-black-trans-10,rgba(0,0,0,.5));z-index:999999;display:flex;align-items:center;justify-content:center;';
const panel = document.createElement('div');
panel.style.cssText = 'background:var(--e-panel-background-color);color:var(--e-foreground-color);border:var(--e-border);border-radius:var(--e-border-radius);padding:var(--e-panel-padding);min-width:320px;max-width:420px;max-height:80vh;overflow:auto;font-family:var(--e-font-interface);box-shadow:0 10px 40px var(--e-black-trans-10,rgba(0,0,0,.4));';
const heading = document.createElement('div');
heading.textContent = title;
heading.style.cssText = 'font-size:var(--e-font-size-l);font-weight:var(--e-heading-font-weight);margin-bottom:var(--e-content-gap);color:var(--e-component);';
panel.appendChild(heading);
const cleanup = (val) => {
document.removeEventListener('keydown', onKey);
overlay.remove();
resolve(val);
};
const onKey = (ev) => {
if (ev.key === 'Escape') return cleanup(null);
const n = parseInt(ev.key);
if (n >= 1 && n <= items.length) cleanup(items[n - 1]);
};
document.addEventListener('keydown', onKey);
items.forEach((item, i) => {
const btn = document.createElement('button');
btn.style.cssText = 'display:flex;justify-content:space-between;gap:var(--e-space-m);width:100%;text-align:left;background:var(--e-base-light);color:var(--e-foreground-color);border:var(--e-border);border-radius:var(--e-border-radius);padding:var(--e-input-padding-block) var(--e-input-padding-inline);margin-bottom:var(--e-space-xs);cursor:pointer;font-size:var(--e-font-size-m);font-family:var(--e-font-interface);transition:var(--e-transition);';
const label = document.createElement('span');
label.textContent = `${i + 1}. ${item.label}`;
btn.appendChild(label);
let sub;
if (item.sublabel) {
sub = document.createElement('span');
sub.textContent = item.sublabel;
sub.style.cssText = 'color:var(--e-foreground-color-muted);font-size:var(--e-font-size-s);white-space:nowrap;';
btn.appendChild(sub);
}
btn.onmouseenter = () => {
btn.style.background = 'var(--e-component-hover)';
btn.style.color = 'var(--e-text-color-dark)';
if (sub) sub.style.color = 'var(--e-text-color-dark)';
};
btn.onmouseleave = () => {
btn.style.background = 'var(--e-base-light)';
btn.style.color = 'var(--e-foreground-color)';
if (sub) sub.style.color = 'var(--e-foreground-color-muted)';
};
btn.onclick = () => cleanup(item);
panel.appendChild(btn);
});
const hint = document.createElement('div');
hint.textContent = 'Click an option, or type its number. Esc to cancel.';
hint.style.cssText = 'color:var(--e-foreground-color-muted);font-size:var(--e-font-size-s);margin-top:var(--e-space-s);';
panel.appendChild(hint);
overlay.appendChild(panel);
overlay.onclick = (ev) => { if (ev.target === overlay) cleanup(null); };
document.body.appendChild(overlay);
});
const toast = (msg, ok = true) => {
const el = document.createElement('div');
el.textContent = msg;
el.style.cssText = `position:fixed;bottom:20px;right:20px;z-index:999999;background:${ok ? 'var(--e-success)' : 'var(--e-danger)'};color:var(--e-text-color-dark);padding:var(--e-input-padding-block) var(--e-space-l);border-radius:var(--e-border-radius);font-family:var(--e-font-interface);font-size:var(--e-font-size-m);box-shadow:0 6px 20px var(--e-black-trans-10,rgba(0,0,0,.3));`;
document.body.appendChild(el);
setTimeout(() => el.remove(), 4000);
};
const waitForComponent = () => new Promise((resolve) => {
toast('Select a component instance on the canvas…');
const check = () => {
const id = e.blocks.getSelectedId();
if (id) {
const b = e.blocks.getJson(id);
if (b.type === 'etch/component') return resolve(b);
}
requestAnimationFrame(check);
};
check();
});
const block = await waitForComponent();
const component = e.components.getJson(block.componentId);
const getOptions = (p) => (p.options?.length ? p.options : (p.selectOptionsString || '')
.split('n').map((l) => l.trim()).filter(Boolean)
.map((l) => { const i = l.indexOf(':'); return i === -1 ? l : l.slice(i + 1).trim(); }));
const walkProps = (props, path = '') => props.flatMap((p) => [
...(p.type?.specialized === 'select' ? [{ path: path + p.key, prop: p, options: getOptions(p) }] : []),
...(p.properties ? walkProps(p.properties, path + p.key + '.') : []),
]);
const selectProps = walkProps(component.properties);
if (!selectProps.length) return toast(`"${component.name}" has no select props.`, false);
const propChoice = await M(
`${component.name} — choose a select prop`,
selectProps.map((x) => ({ label: x.path, sublabel: `default: ${x.prop.default ?? 'none'}`, ...x }))
);
if (!propChoice) return toast('Cancelled.', false);
const valueChoice = await M(
`${propChoice.path} — choose a new default`,
propChoice.options.map((o) => ({ label: o, value: o }))
);
if (!valueChoice) return toast('Cancelled.', false);
const findProp = (props, path) => {
const [head, ...rest] = path.split('.');
for (const p of props) {
if (p.key !== head) continue;
if (!rest.length) return p;
if (p.properties) return findProp(p.properties, rest.join('.'));
return null;
}
return null;
};
const props = structuredClone(component.properties);
findProp(props, propChoice.path).default = valueChoice.value;
await e.components.updateAsync(block.componentId, { properties: props });
const verify = findProp(e.components.getJson(block.componentId).properties, propChoice.path);
const success = verify?.default === valueChoice.value;
const msg = success ? `✅ ${propChoice.path} = ${valueChoice.value}` : `⚠️ Mismatch: ${verify?.default}`;
toast(msg, success);
console.log(msg);
})();