good-old-ui
v0.0.32
Published
A retro Windows 3.11 / MS-DOS UI component library for Vue 3
Maintainers
Readme
good-old-ui
A retro UI component library for Vue 3. Ships two built-in themes:
| Theme | Look | |---|---| | Windows 3.11 (default) | Chunky 3-D bevelled borders, teal desktop, grey panels | | MS-DOS | Flat CGA-blue panels, monospace font, cyan selection |
Switch themes at runtime with the useTheme() composable — no page reload required.
Components
| Component | Description |
|---|---|
| GouButton | Raised 3-D button with pressed state |
| GouInput | Deep-inset single-line text field |
| GouTextarea | Deep-inset multi-line text area with retro scrollbar |
| GouCheckbox | Square checkbox with ✓ mark |
| GouRadio | Circular radio button |
| GouSelect | Combo-box dropdown with keyboard nav |
| GouProgressBar | Segmented progress bar (+ indeterminate mode) |
| GouSlider | Drag-based horizontal range slider |
| GouTabs + GouTabPanel | Tabbed panel container |
| GouMenuBar | Application menu bar with dropdowns |
| GouDialog | Modal dialog with stipple overlay |
| GouTooltip | Hover tooltip wrapper |
| GouLoader | Block or spinner loader with optional full-screen overlay |
| GouDesktop | Full-screen desktop workspace container |
| GouWindow | Draggable, resizable, minimisable window |
Quick Start
1. Install
npm install good-old-ui2. Load styles globally
In your app entry (main.ts) before calling createApp — order matters:
// 1. Structural resets — always required
import 'good-old-ui/dist/css/good-old-base.css'
// 2. Windows 3.11 theme — required as the default
import 'good-old-ui/dist/css/good-old-theme-win311.css'
// 3. MS-DOS theme — import if you want runtime theme switching
import 'good-old-ui/dist/css/good-old-theme-msdos.css'
// 4. Bundled component scoped styles
import 'good-old-ui/dist/css/good-old-ui.css'Note: The theme CSS files (items 1–3) define CSS custom property tokens (
--gou-*). The bundled component CSS (item 4) contains only scoped styles that consume those tokens. All four files are separate by design so you can opt out of any theme you don't need.
Prevent theme flash on page reload (recommended)
When users can persist their theme preference, add this inline script to
index.html before any <link> or <script> tags so the browser applies
the correct data-gou-theme attribute on the very first paint:
<script>
(function () {
var valid = ['win311', 'msdos'];
var stored = localStorage.getItem('gou-theme');
document.documentElement.setAttribute(
'data-gou-theme',
valid.indexOf(stored) !== -1 ? stored : 'win311'
);
})();
</script>3. Wrap your app in gou-scope
The library's CSS resets and scrollbar styles are scoped to .gou-scope to
avoid bleeding into the host page:
<template>
<div class="gou-scope" style="width: 100vw; height: 100vh;">
<GouDesktop>
<!-- windows live here -->
</GouDesktop>
</div>
</template>4. Import and use components
<script setup lang="ts">
import {
GouDesktop, GouWindow,
GouButton, GouInput, GouCheckbox, GouRadio,
GouSelect, GouProgressBar, GouSlider,
GouTabs, GouTabPanel,
GouMenuBar, GouDialog, GouTooltip,
useTheme,
} from 'good-old-ui'
import { ref } from 'vue'
const { currentTheme, themes, setTheme, toggleTheme } = useTheme()
const name = ref('')
const agreed = ref(false)
const progress = ref(30)
const tab = ref('tab1')
const dialog = ref(false)
const menus = [
{
label: 'File',
items: [
{ label: 'New', action: () => console.log('New') },
{ separator: true },
{ label: 'Exit', action: () => console.log('Exit') },
],
},
{ label: 'Help', items: [{ label: 'About', action: () => (dialog.value = true) }] },
]
</script>
<template>
<div class="gou-scope" style="width:100vw;height:100vh;">
<GouDesktop>
<GouWindow title="Demo" window-id="demo"
:initial-x="40" :initial-y="40"
:initial-width="480" :initial-height="380">
<GouMenuBar :menus="menus" />
<div style="padding:8px;display:flex;flex-direction:column;gap:8px;">
<GouInput v-model="name" placeholder="Your name…" />
<GouCheckbox v-model="agreed" label="I accept the terms" />
<GouProgressBar :value="progress" :label="`${progress}%`" />
<GouSlider v-model="progress" :show-value="true" />
<GouTabs v-model="tab"
:tabs="[{ id:'tab1', label:'General' }, { id:'tab2', label:'Advanced' }]">
<GouTabPanel id="tab1">General settings.</GouTabPanel>
<GouTabPanel id="tab2">Advanced settings.</GouTabPanel>
</GouTabs>
<!-- Theme switcher -->
<div style="display:flex;gap:6px;">
<GouButton
v-for="t in themes" :key="t.id" :label="t.label"
@click="setTheme(t.id)"
/>
</div>
</div>
</GouWindow>
<GouDialog v-model="dialog" title="About good-old-ui" :width="300">
<p>good-old-ui — a retro Vue 3 component library.</p>
<template #footer>
<GouButton label="OK" @click="dialog = false" />
</template>
</GouDialog>
</GouDesktop>
</div>
</template>Themes & Runtime Switching
useTheme() composable
import { useTheme } from 'good-old-ui'
const { currentTheme, themes, setTheme, toggleTheme } = useTheme()| Return | Type | Description |
|---|---|---|
| currentTheme | Readonly<Ref<GouTheme>> | Reactive current theme id |
| themes | readonly ThemeDescriptor[] | All available themes with labels |
| setTheme(id) | (id: GouTheme) => void | Switch immediately + persist to localStorage |
| toggleTheme() | () => void | Cycle through all themes in order |
setTheme('msdos') // switch to MS-DOS
setTheme('win311') // switch back to Windows 3.11
toggleTheme() // win311 → msdos → win311 → …How it works
setTheme() writes data-gou-theme="<id>" to <html>. The MS-DOS theme CSS
uses [data-gou-theme="msdos"] selectors (higher specificity than :root) to
override the Win311 CSS custom properties. One attribute on <html> instantly
re-tokens every component — no page reload, no per-component class toggling.
Custom themes
Override any --gou-* token under your own attribute:
[data-gou-theme="amber"] {
--gou-color-bg: #1a0e00;
--gou-color-fg: #ffaa00;
--gou-color-desktop: #000000;
--gou-font-family: "Courier New", monospace;
--gou-text-shadow-emboss: none;
/* … */
}Then call setTheme('amber' as GouTheme) (or extend the GouTheme union type).
CSS Architecture
Published file layout
After npm run build, the dist/ directory contains:
dist/
├── js/
│ ├── good-old-ui.js # ESM bundle (import)
│ └── good-old-ui.umd.cjs # UMD bundle (require / <script>)
├── css/
│ ├── good-old-ui.css # Bundled component scoped styles
│ ├── good-old-base.css # Structural resets (layout, focus, scrollbar hooks)
│ ├── good-old-theme-win311.css # Windows 3.11 tokens + shadows
│ └── good-old-theme-msdos.css # MS-DOS tokens + flat borders
└── index.d.ts # TypeScript declarationsWhat each CSS file contains
| File | Contains | Import |
|---|---|---|
| good-old-base.css | Box-sizing, font, focus, scrollbar structure | Always required |
| good-old-theme-win311.css | Win 3.11 --gou-* tokens, 3-D shadow vars | Default theme |
| good-old-theme-msdos.css | MS-DOS --gou-* token overrides, flat borders | For switching |
| good-old-ui.css | Scoped component styles (consume --gou-* tokens) | Always required |
good-old-ui.css(the bundled output) does not include the token files. Import the theme CSS files separately.
CSS custom property tokens
| Token | Purpose |
|---|---|
| --gou-color-bg | Window / panel background |
| --gou-color-fg | Default text colour |
| --gou-color-desktop | Desktop background |
| --gou-color-active-title | Active window title bar background |
| --gou-color-highlight | Selected / focused item background |
| --gou-color-input-bg | Text input background |
| --gou-color-tooltip-bg | Tooltip background |
| --gou-color-tooltip-fg | Tooltip text colour |
| --gou-font-family | Base font stack |
| --gou-font-size | Base font size |
| --gou-shadow-raised | 3-D raised surface shadow |
| --gou-shadow-sunken | 3-D sunken / pressed shadow |
| --gou-shadow-deep-inset | Deep inset (text inputs, checkboxes) |
| --gou-shadow-tab | Tab strip 3-D border |
| --gou-shadow-footer-inset | Dialog footer separator |
| --gou-shadow-separator | Menu separator highlight |
| --gou-text-shadow-emboss | Disabled text drop-shadow (none in flat themes) |
| --gou-color-border | Generic 1-px border colour |
| --gou-color-shadow-mid | Mid-tone for separators and borders |
3-D border approach (Win311)
Win311's bevelled look uses four-layer box-shadow — no border property:
inset 1px 1px 0 #ffffff ← inner top-left highlight
inset -1px -1px 0 #808080 ← inner bottom-right shadow
1px 1px 0 #000000 ← outer bottom-right black edge
-1px -1px 0 #dfdfdf ← outer top-left light edgeInvert the inner layers for the pressed state. The MS-DOS theme replaces all of
this with 0 0 0 1px <border-color> (single flat outline).
Development
Setup
git clone https://github.com/yourname/good-old-ui.git
cd good-old-ui
npm installDev server
npm run devOpens the interactive demo at http://localhost:5173. The Display Properties
window (bottom-left, below the Component Gallery) contains the theme switcher.
Type-check
npm run type-checkTests
npm test # run once
npm run test:watch # watch modeBuilding for Production
npm run buildOutputs to dist/ as shown above. The build:
- Type-checks (
vue-tsc --noEmit) - Bundles components into
dist/js/and scoped CSS intodist/css/ - Copies the three theme CSS source files into
dist/css/ - Generates
dist/index.d.ts(single rolled-up declaration file)
Publishing to npm
# Bump version (patch | minor | major)
npm version patch
# Build
npm run build
# Publish
npm publish --access publicThe "files": ["dist"] field in package.json ensures only the dist/
directory is included in the published package — no source files, test files,
or config.
Versioning
| Change type | Bump |
|---|---|
| Bug fix, style tweak | patch |
| New component, new theme, new prop | minor |
| Breaking API change | major |
License
MIT
