Live: try it with the mouse and the keyboard (Tab, arrows, Escape).

Popover UI component for websites and web apps, shown in Patchbay, a fictional developer product. Shows a share button that opens a non-modal popover with a short form, a disabled trigger, and every state (default, hover, focus-visible, open, disabled). Retro style: warm cream, soft Fraunces headings with DM Sans, outlined cards and offset colour shadows. Comes with copyable HTML, CSS and a few lines of plain JavaScript (keyboard support and ARIA per the WAI-ARIA Authoring Practices), a live demo, and a DESIGN.md with light and dark colour tokens.
#fffcf8 · surface #fff0df · text #231505 · muted #72604b · primary #ba474e · accent #527c30#1e1306 · surface #2b1e0e · text #f4ebe1 · muted #bfae9a · primary #fd8f90 · accent #8ebb6ePaste the HTML where the component goes, the CSS into your stylesheet and the script before </body>. The tokens block holds the light and dark colours; swap in your own.
<div class="mgd-pop-row">
<div class="mgd-pop">
<button class="mgd-btn" type="button" aria-expanded="false" aria-controls="share-pop"><svg class="i" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.7" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><circle cx="6" cy="12" r="2.5"/><circle cx="18" cy="6" r="2.5"/><circle cx="18" cy="18" r="2.5"/><path d="M8.2 10.8l7.6-3.6M8.2 13.2l7.6 3.6"/></svg> Share</button>
<div class="mgd-pop__panel" id="share-pop" role="dialog" aria-labelledby="share-title" tabindex="-1" hidden>
<h3 class="mgd-pop__title" id="share-title">Share “API latency”</h3>
<p class="mgd-pop__hint">People you invite can view this dashboard.</p>
<label class="mgd-pop__label" for="share-email">Email</label>
<input class="mgd-pop__input" id="share-email" type="email" autocomplete="email" placeholder="name@company.com">
<div class="mgd-pop__actions"><button type="button" class="mgd-btn" data-pop-close>Cancel</button><button type="button" class="mgd-btn mgd-btn--primary">Send invite</button></div>
</div>
</div>
<button class="mgd-btn" type="button" disabled><svg class="i" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.7" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M12 4v12M7 11l5 5 5-5M4 20h16"/></svg> Export (Pro)</button>
</div>/* Tokens (light + dark) */
:root{--bg:#fffcf8;--surface:#fff0df;--border:#e1cfba;--text:#231505;--muted:#72604b;--primary:#ba474e;--on-primary:#ffffff;--accent:#527c30;--font-h:"Fraunces",Georgia,serif;--font-b:"DM Sans",system-ui,sans-serif;--hw:700;
--c-bw:2px;--c-line:var(--text);--c-r:10px;--c-pill:10px;--c-sh:3px 3px 0 var(--primary);--c-pop:5px 5px 0 var(--text);--c-tt:none;--c-ls:0;--c-fw:700;--c-fs:14px;--c-ring:3px solid var(--accent);--c-ind:3px;color-scheme:light}
@media (prefers-color-scheme:dark){:root:not([data-theme="light"]){--bg:#1e1306;--surface:#2b1e0e;--border:#423424;--text:#f4ebe1;--muted:#bfae9a;--primary:#fd8f90;--on-primary:#111111;--accent:#8ebb6e;color-scheme:dark}
}
:root[data-theme="dark"]{--bg:#1e1306;--surface:#2b1e0e;--border:#423424;--text:#f4ebe1;--muted:#bfae9a;--primary:#fd8f90;--on-primary:#111111;--accent:#8ebb6e;color-scheme:dark}
/* Component */
.mgd-sr{position:absolute!important;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0 0 0 0);white-space:nowrap;border:0}
.mgd-btn{display:inline-flex;align-items:center;justify-content:center;gap:8px;min-height:40px;padding:0 16px;border:var(--c-bw) solid var(--c-line);border-radius:var(--c-pill);background:var(--surface);color:var(--text);font:var(--c-fw) var(--c-fs)/1 var(--font-b);text-transform:var(--c-tt);letter-spacing:var(--c-ls);box-shadow:var(--c-sh);cursor:pointer;white-space:nowrap;transition:background-color .12s,border-color .12s}
.mgd-btn:hover,.mgd-btn.is-hover{background:color-mix(in oklab,var(--primary) 9%,var(--surface))}
.mgd-btn:focus-visible,.mgd-btn.is-focus{outline:var(--c-ring);outline-offset:2px}
.mgd-btn[aria-expanded="true"],.mgd-btn.is-open{background:color-mix(in oklab,var(--primary) 15%,var(--surface))}
.mgd-btn--primary{background:var(--primary);border-color:var(--primary);color:var(--on-primary)}
.mgd-btn--primary:hover,.mgd-btn--primary.is-hover{background:color-mix(in oklab,var(--primary) 84%,var(--text))}
.mgd-btn:disabled,.mgd-btn[aria-disabled="true"],.mgd-btn.is-disabled{opacity:.5;cursor:not-allowed;box-shadow:none}
.mgd-btn svg{flex:none}
.mgd-pop{position:relative;display:inline-block}
.mgd-pop__panel{position:absolute;z-index:10;top:calc(100% + 10px);left:0;width:min(340px,calc(100vw - 40px));padding:18px;background:var(--surface);color:var(--text);border:var(--c-bw) solid var(--c-line);border-radius:var(--c-r);box-shadow:var(--c-pop)}
.mgd-pop__panel[hidden]{display:none}
.mgd-pop__panel:focus-visible{outline:var(--c-ring);outline-offset:2px}
.mgd-pop__title{margin:0 0 4px;font:var(--hw) 17px/1.25 var(--font-h)}
.mgd-pop__hint{margin:0 0 14px;color:var(--muted);font-size:13px}
.mgd-pop__label{display:block;margin:0 0 6px;font:600 13px var(--font-b)}
.mgd-pop__input{display:flex;align-items:center;width:100%;height:42px;padding:0 12px;border:1px solid var(--text);border-radius:var(--c-r);background:var(--bg);color:var(--text);font:15px var(--font-b)}
.mgd-pop__input::placeholder{color:var(--muted)}
.mgd-pop__input:focus-visible{outline:var(--c-ring);outline-offset:1px}
.mgd-pop__actions{display:flex;justify-content:flex-end;gap:8px;margin-top:16px}
.mgd-pop-row{display:flex;flex-wrap:wrap;gap:12px;align-items:center}
@media (prefers-reduced-motion:reduce){.mgd-pop,.mgd-pop *{transition:none!important;animation-duration:2.4s!important}}document.querySelectorAll(".mgd-pop").forEach(function (wrap) {
var btn = wrap.querySelector("[aria-controls]"), panel = document.getElementById(btn.getAttribute("aria-controls"));
function open() { panel.hidden = false; btn.setAttribute("aria-expanded", "true"); (panel.querySelector("input,select,textarea,button") || panel).focus(); }
function close(back) { if (panel.hidden) return; panel.hidden = true; btn.setAttribute("aria-expanded", "false"); if (back) btn.focus(); }
btn.addEventListener("click", function () { panel.hidden ? open() : close(true); });
panel.addEventListener("click", function (e) { if (e.target.closest("[data-pop-close]")) close(true); });
wrap.addEventListener("keydown", function (e) { if (e.key === "Escape" && !panel.hidden) { e.stopPropagation(); close(true); } });
document.addEventListener("click", function (e) { if (!wrap.contains(e.target)) close(false); });
wrap.addEventListener("focusout", function (e) { if (e.relatedTarget && !wrap.contains(e.relatedTarget)) close(false); });
});## Component: Popover
A small non-modal panel anchored to a button, for a short form or details. Escape or a click outside closes it.
- **Pattern:** WAI-ARIA Authoring Practices, Dialog (non-modal disclosure) (https://www.w3.org/WAI/ARIA/apg/patterns/dialog-modal/)
- **Roles and attributes:** The trigger is a button with aria-expanded and aria-controls; the panel has role="dialog" and aria-labelledby, is not modal, and takes focus on open. Escape closes it and returns focus to the trigger.
- **States drawn:** default, hover, focus-visible, open, disabled
- **Focus:** a visible `:focus-visible` outline (3px solid accent), never removed
- **Style knobs (retro):** border 2px, corners 10px, shadow `3px 3px 0 var(--primary)`, labels sentence case
- **Motion:** transitions are short (≤160ms) and switched off under `prefers-reduced-motion: reduce`
- **Code:** HTML + CSS + a few lines of plain JavaScript (no libraries); colours come only from the tokens above, so light and dark both work
### Keyboard
- **Enter / Space:** open or close from the trigger
- **Tab:** move through the panel's controls
- **Escape:** close and return focus to the trigger
### Licence
Original Made Good Designs code: free to use and adapt in any project, including commercial ones, with no credit needed. Fonts are Google Fonts under their own licences.Add the Made Good Designs library to your assistant once. Then ask for UI references, brand palettes, font pairings and design tokens in any chat.
https://madegooddesigns.com/inspiration/mcp?ref=ui-popover-uiSettings → Connectors → Add custom connector → paste the URL.
Settings → Apps & Connectors → Advanced → Developer mode → Create → paste the URL.
One click, then confirm in Cursor.
One click, then confirm in VS Code (Copilot agent mode).
claude mcp add --transport http made-good-designs https://madegooddesigns.com/inspiration/mcp?ref=ui-popover-ui
gemini mcp add --transport http made-good-designs https://madegooddesigns.com/inspiration/mcp?ref=ui-popover-ui
Add this to the app's MCP config (Windsurf: mcp_config.json).
Fonts: Fraunces + DM Sans (Google Fonts)
Prompt copied: paste it into the chat that just opened.
Writing about this component?
search_ui_screens (e.g. “billing settings, editorial style”), get_ui_flow for a whole journey, and get_ui_screen_code to pull the HTML and DESIGN.md straight into your project.