Tutorial: a flat design mywant-gui
The built-in grid design draws overlays as full-bleed coloured tiles on a dark box. This tutorial builds Flat — a light card, cells as separate rounded tiles with a gap between them, solid colours, words in their own case — and ships it as an extension that installs beside the GUI with no build step.
A design can also be shipped as a design custom, installed with mywant custom install owner/repo — see Tutorial: a design plugin, which compares the two. The finished extension is in this site's repository: examples/overlay-flat.
overlay-flat/
├── gui-extension.json the manifest
├── overlay-flat.js registers the design
└── overlay-flat.css what its class names look like1. The manifest
{
"name": "overlay-flat",
"version": "0.1.0",
"script": "overlay-flat.js",
"styles": ["overlay-flat.css"]
}No requires: the extension uses only the design API, so it does not pin a mywant-gui version. See Runtime extensions.
2. The design
A design is only class names (Designs). Flat prefixes its own with mwf- and registers through the extension API, which mywant-gui publishes on window.__mywantModules before it loads any extension:
/*
* overlay-flat — a flat overlay design for mywant-gui, as a runtime extension.
*
* No build step: mywant-gui loads this file as a plain script before its first
* render, with its own modules published on window.__mywantModules. The design
* is only class names; what they look like is in overlay-flat.css, which the
* manifest lists under "styles". (The app's Tailwind build contains only the
* classes the app itself uses, so a design from outside brings its own.)
*
* Install: copy this directory to ~/.mywant/gui-extensions/overlay-flat and
* reload the GUI. Pick "Flat" under Overlay Design in your character's display.
*/
(function () {
var design = {
id: 'flat',
name: 'Flat',
tones: {
confirm: 'mwf-confirm',
cancel: 'mwf-cancel',
danger: 'mwf-danger',
primary: 'mwf-primary',
caution: 'mwf-caution',
info: 'mwf-info',
accent: 'mwf-accent',
special: 'mwf-special',
muted: 'mwf-muted',
},
disabled: 'mwf-disabled',
cell: 'mwf-cell',
cellInteractive: 'mwf-interactive',
cellOff: 'mwf-off',
focus: 'mwf-focus',
label: 'mwf-label',
header: 'mwf-header',
surface: 'mwf-surface',
frame: 'mwf-frame',
tailColor: 'var(--mwf-outline)',
backdrop: 'mwf-backdrop',
input: 'mwf-input',
enterAnimation: 'mwf-in 120ms ease-out forwards',
cellEnterAnimation: 'mwf-cell-in 140ms ease-out both',
// The same look as values, for menus drawn where this stylesheet is not
// loaded — the browser extension's and the bookmarklet's, on other pages.
// Stored with the choice; they apply it as CSS custom properties.
portable: {
tones: {
confirm: '#10b981', cancel: '#94a3b8', danger: '#ef4444', primary: '#3b82f6',
caution: '#f59e0b', info: '#0ea5e9', accent: '#6366f1', special: '#8b5cf6', muted: '#64748b',
},
surface: '#ffffff',
outline: '#e2e8f0',
ink: '#334155',
radius: 14,
cellRadius: 10,
cellGap: 3,
labelTransform: 'none',
labelWeight: 600,
labelSize: 10,
backdrop: 'rgba(255,255,255,.6)',
},
};
var registry = window.__mywantModules && window.__mywantModules['@/extensions/registry'];
if (registry && registry.registerExtension) {
// As an extension — the way anything added to the GUI is added.
registry.registerExtension({ id: 'overlay-flat', overlayDesigns: [design] });
} else if (window.__mywant && window.__mywant.registerOverlayDesign) {
window.__mywant.registerOverlayDesign(design);
}
})();The portable block is the same look as values: the browser extension and the bookmarklet draw their menus with it on other people's pages, where overlay-flat.css is not loaded (see Designs).
3. The stylesheet
The classes the design names. The app's Tailwind build would not contain them, which is why a design from outside brings its own. Dark mode follows the app's dark class on <html>; the focus ring takes whoever holds the keys' colour from --mw-focus-ring:
/*
* overlay-flat — the look behind the class names overlay-flat.js registers.
*
* Flat: a light card instead of a dark box, cells as separate rounded tiles
* with a gap between them, solid colours, no transparency, no glow, words in
* their own case. The app puts `dark` on <html> in dark mode.
*/
:root {
--mwf-surface: #ffffff;
--mwf-outline: #e2e8f0;
--mwf-ink: #334155;
--mwf-field: #f8fafc;
--mwf-backdrop: rgba(255, 255, 255, 0.6);
}
.dark {
--mwf-surface: #1e293b;
--mwf-outline: #334155;
--mwf-ink: #e2e8f0;
--mwf-field: #0f172a;
--mwf-backdrop: rgba(15, 23, 42, 0.6);
}
/* The box, and its surface for other shapes (a pill). */
.mwf-surface { background: var(--mwf-surface); border: 1px solid var(--mwf-outline); }
.mwf-frame {
background: var(--mwf-surface);
border: 1px solid var(--mwf-outline);
border-radius: 14px;
overflow: hidden;
box-shadow: 0 6px 20px rgba(15, 23, 42, 0.12);
}
.mwf-backdrop { background: var(--mwf-backdrop); backdrop-filter: blur(2px); }
/* Cells: separate tiles with a gap, instead of one full-bleed block. */
.mwf-cell {
margin: 3px;
width: calc(100% - 6px) !important;
height: calc(100% - 6px) !important;
border-radius: 10px;
}
.mwf-interactive { transition: filter 120ms, transform 120ms; }
.mwf-interactive:hover { filter: brightness(1.07); }
.mwf-interactive:active { transform: scale(0.97); }
.mwf-off { opacity: 0.5; }
.mwf-disabled { background: var(--mwf-outline); opacity: 0.6; cursor: not-allowed; }
/* The ring's colour is whoever holds the keys (--mw-focus-ring). */
.mwf-focus { box-shadow: 0 0 0 3px var(--mw-focus-ring, #38bdf8); }
/* Tones — solid, one step lighter than the grid's. */
.mwf-confirm { background: #10b981; }
.mwf-cancel { background: #94a3b8; }
.mwf-danger { background: #ef4444; }
.mwf-primary { background: #3b82f6; }
.mwf-caution { background: #f59e0b; }
.mwf-info { background: #0ea5e9; }
.mwf-accent { background: #6366f1; }
.mwf-special { background: #8b5cf6; }
.mwf-muted { background: #64748b; }
/* Words. */
.mwf-label { color: #fff; font-size: 10px; font-weight: 600; line-height: 1; }
.mwf-header {
display: flex; align-items: center; gap: 6px;
color: var(--mwf-ink); font-size: 12px; font-weight: 600;
}
.mwf-input {
width: 100%;
border-radius: 8px;
background: var(--mwf-field);
border: 1px solid var(--mwf-outline);
padding: 4px 10px;
font-size: 14px;
color: var(--mwf-ink);
outline: none;
}
.mwf-input:focus { border-color: #0ea5e9; }
/* Coming in. */
@keyframes mwf-in { from { opacity: 0; transform: translateY(4px); } to { opacity: 1; transform: none; } }
@keyframes mwf-cell-in { from { opacity: 0; transform: scale(0.94); } to { opacity: 1; transform: none; } }4. Install it
cp -R examples/overlay-flat ~/.mywant/gui-extensions/overlay-flat
# reload the GUI in the browser
curl -s localhost:8081/api/v1/gui-extensions # lists overlay-flat5. Pick it
Open Settings → Overlay Design, or your character's Display tab on the Characters page, and choose Flat. Every overlay changes at once — the card long-press menus, Shift+Enter, the yes/no questions and the board's bubbles — because every one of them is drawn from the overlay library.
The choice is stored on the character, at display.ext.overlay.design; with no character chosen in the browser, Settings keeps it in the browser instead.
Making it yours
- Change only what you care about: fields you leave out of a design are taken from
grid. - Keep the tones' meanings. A design decides what
dangerlooks like, never which actions are dangerous. - Keep the cell's size. The layout is shared by every design; restyle the cell's shape inside it (
margin,border-radius), as Flat does.