@abvx/ascii-theme
v0.2.0
Published
Framework-agnostic ASCII theme layer with style/mode toggles and box-drawing stickers.
Readme
AsciiTheme
AsciiTheme is a framework-agnostic micro-package that adds an ASCII visual layer to existing pages. It provides:
data-style="default|ascii"management- optional mode management (
light|dark) - box-drawing sticker rendering for
[data-ascii-sticker]
Runtime has no dependencies.
dist/ is committed to git for CDN convenience and reproducible release snapshots.
Install
npm
npm install @abvx/ascii-themeimport { initAsciiTheme } from "@abvx/ascii-theme";
import "@abvx/ascii-theme/style.css";
initAsciiTheme();Vite (React or vanilla)
import { initAsciiTheme } from "@abvx/ascii-theme";
import "@abvx/ascii-theme/style.css";
initAsciiTheme({ managedMode: false, base: false });Next.js (App Router / Pages Router)
Import CSS once in app/layout.tsx or pages/_app.tsx:
import "@abvx/ascii-theme/style.css";Run init only on the client:
"use client";
import { useEffect } from "react";
import { initAsciiTheme } from "@abvx/ascii-theme";
export function AsciiThemeBoot() {
useEffect(() => {
initAsciiTheme();
}, []);
return null;
}CDN
<link rel="stylesheet" href="https://unpkg.com/@abvx/[email protected]/dist/style.css" />
<script src="https://unpkg.com/@abvx/[email protected]/dist/ascii-theme.umd.js"></script>
<script>
AsciiTheme.initAsciiTheme({ managedMode: false });
</script>If npm install is unavailable in a host project, use a pinned GitHub commit via jsDelivr:
<link rel="stylesheet" href="https://cdn.jsdelivr.net/gh/markoblogo/AsciiTheme@<commit>/dist/style.css" />
<script src="https://cdn.jsdelivr.net/gh/markoblogo/AsciiTheme@<commit>/dist/ascii-theme.umd.js"></script>Use a commit hash or release tag, not @main.
Basic usage (respect host theme)
By default, the package does not control your host light/dark theme.
It reads the host theme attribute (data-theme by default):
<html data-theme="dark">import { initAsciiTheme } from "@abvx/ascii-theme";
initAsciiTheme({ managedMode: false });ASCII palette mapping uses host theme selectors:
:root[data-style="ascii"][data-theme="light"]:root[data-style="ascii"][data-theme="dark"]
Managed mode usage
If you want the plugin to control mode itself:
import {
initAsciiTheme,
toggleAsciiMode,
} from "@abvx/ascii-theme";
initAsciiTheme({ managedMode: true, defaultMode: "light" });
document.getElementById("mode-btn")?.addEventListener("click", () => {
toggleAsciiMode();
});Managed mode uses data-ascii-mode="light|dark" on :root.
Managed theme + injected toggles (for sites without light/dark)
Use this when the host site has no built-in theme switch and you want the plugin to mount compact controls in a header container:
import { initAsciiTheme } from "@abvx/ascii-theme";
initAsciiTheme({
managedMode: true,
defaultMode: "dark",
defaultStyle: "default",
addThemeToggle: true,
addStyleToggle: true,
mountSelector: "header .right",
mountPlacement: "append",
className: "header-button",
});Notes:
- Toggles are injected only when
mountSelectoris provided and toggle flags are enabled. - Theme toggle switches
dark/light; style toggle text switchesASCII/Default. - When
base: true, style toggle is automatically disabled anddata-styleis forced toascii.
Smart integration (auto detect host theme)
Use integrateTheme to control how mode is handled:
integrateTheme: "respect": always use host theme; never inject plugin theme toggle.integrateTheme: "managed": plugin controlsdata-ascii-modeand can inject both toggles.integrateTheme: "auto"(default): if host theme is detected, plugin switches to respect mode and disables injected theme toggle automatically.
For sites that already have their own light/dark switch:
initAsciiTheme({
integrateTheme: "auto",
addThemeToggle: true, // auto mode disables this when host theme is detected
addStyleToggle: true,
mountSelector: ".header-controls",
});For sites without host theme:
initAsciiTheme({
integrateTheme: "managed",
managedMode: true,
defaultMode: "dark",
addThemeToggle: true,
addStyleToggle: true,
mountSelector: ".header-controls",
});Integration standard (theme + ASCII)
For cross-project consistency, follow the standard integration smoke-check:
- Copy
templates/theme-smoke-check.mjsinto your host project and run it asnpm run smoke:theme. - Apply the 4-state visual checklist from
docs/integration-smoke-check.md(default/asciixlight/dark). - Use
integrateTheme: "respect"for sites that already own light/dark; useintegrateTheme: "managed"only for sites without host theme controls.
No host-theme + hardcoded colors playbook
Use this order to avoid contrast regressions on utility-heavy sites:
- Start with
integrateTheme: "auto"andaddThemeToggle: true+addStyleToggle: true. - If no host theme is detected, switch to managed mode (
integrateTheme: "managed",defaultMode: "dark"). - Run the 4-state smoke check (
default/light,default/dark,ascii/light,ascii/dark). - If contrast is still weak in managed mode, rely on built-in readability hardening for common
text-*/bg-*/border-*utility classes before adding site-local CSS. - Add site-local bridge CSS only for truly project-specific tokens that cannot be generalized.
- Hardcoded Tailwind arbitrary tokens (for example
text-[#111827],bg-[#F9FAFB],border-[#E5E7EB]) are normalized by default in managed dark and ASCII modes.
Use AsciiTheme as your only CSS (base preset)
When you want an ASCII-first site with no separate style axis, use the base preset.
In this mode ASCII is always on (data-style="ascii"), so you only keep the light/dark toggle.
Vite
import { initAsciiTheme } from "@abvx/ascii-theme";
import "@abvx/ascii-theme/base.css";
initAsciiTheme({
base: true,
managedMode: true,
addThemeToggle: true,
addStyleToggle: false,
mountSelector: ".header-controls",
});Next.js
Import base CSS once in app/layout.tsx or pages/_app.tsx:
import "@abvx/ascii-theme/base.css";Run init in a client component (initAsciiTheme must run client-side):
"use client";
import { useEffect } from "react";
import { initAsciiTheme } from "@abvx/ascii-theme";
export function AsciiThemeBoot() {
useEffect(() => {
initAsciiTheme({
base: true,
managedMode: true,
addThemeToggle: true,
addStyleToggle: false,
mountSelector: ".header-controls",
});
}, []);
return null;
}CDN (pinned):
<link rel="stylesheet" href="https://unpkg.com/@abvx/[email protected]/dist/base.css" />
<script src="https://unpkg.com/@abvx/[email protected]/dist/ascii-theme.umd.js"></script>
<script>
AsciiTheme.initAsciiTheme({
base: true,
managedMode: true,
addThemeToggle: true,
addStyleToggle: false,
mountSelector: ".header-controls",
});
</script>Minimal landing example (base preset)
Base preset is ASCII-only by design, so there is no style toggle in this mode. Keep the light/dark toggle enabled.
<link rel="stylesheet" href="https://unpkg.com/@abvx/[email protected]/dist/base.css" />
<script src="https://unpkg.com/@abvx/[email protected]/dist/ascii-theme.umd.js"></script>
<header class="a-container a-section a-cluster a-between">
<strong>ASCII Landing</strong>
<nav class="a-cluster"><a href="#">Docs</a><a href="#">GitHub</a></nav>
<div id="theme-controls"></div>
</header>
<main class="a-container a-stack a-gap-3">
<section class="a-section a-split a-gap-3">
<div class="a-stack a-gap-2">
<h1 class="a-balance">Ship a terminal-style landing in minutes.</h1>
<p class="a-prose a-muted">Use base.css + utilities only, no extra framework CSS.</p>
<div class="a-cluster a-gap-2"><a class="a-btn--primary" href="#">Start</a><a class="a-btn--ghost" href="#">Read docs</a></div>
</div>
<aside class="a-card">Sidebar panel</aside>
</section>
</main>
<script>
AsciiTheme.initAsciiTheme({
base: true,
managedMode: true,
addThemeToggle: true,
addStyleToggle: false,
mountSelector: "#theme-controls"
});
</script>Utility classes
Core token contract (base preset):
--bg, --fg, --muted, --border, --link, --code-bg, --radius, --pad, --gap, --container, --line, --font, --focus.
Light/dark mapping in managed mode is applied via:
:root[data-ascii-mode="light"](dark blue on white):root[data-ascii-mode="dark"](terminal green on black)
Layout utilities
.a-split: 1 column on mobile, 2 equal columns at>=768px..a-aside: 1 column on mobile, content + sidebar (minmax(0, 1fr) 320px) at>=768px..a-aside--reverse: flips.a-asidecolumn order at>=768px..a-cluster: inline wrapped row withgap: var(--gap)and centered alignment.
| Class | Purpose | Notes |
| --- | --- | --- |
| .a-container | Constrains page width and adds horizontal padding. | Centered with auto margins. |
| .a-section | Vertical spacing for page sections. | Uses --pad for subtle rhythm. |
| .a-stack | Vertical flex layout with configurable gap. | Uses --a-gap (default from --gap). |
| .a-row | Horizontal flex layout with wrapping and configurable gap. | Uses --a-gap (default from --gap). |
| .a-cluster | Inline row layout for nav/tags/footer links. | Wrapped + aligned center. |
| .a-gap-1 | Small gap helper. | Sets --a-gap: 8px. |
| .a-gap-2 | Default gap helper. | Sets --a-gap: 12px. |
| .a-gap-3 | Large gap helper. | Sets --a-gap: 20px. |
| .a-center | Centers text alignment. | text-align: center. |
| .a-right | Right-aligns text. | text-align: right. |
| .a-between | Distributes flex items across available space. | justify-content: space-between. |
| .a-align-center | Centers flex items on cross axis. | align-items: center. |
| .a-wrap | Forces flex wrapping. | Useful for compact control groups. |
| .a-grid | Base grid container with configurable gap. | Uses --a-gap (default from --gap). |
| .a-cols-2 | Responsive 2-column grid. | 1 column by default, 2 columns at >=768px. |
| .a-cols-3 | Responsive 3-column grid. | 1 column by default, 3 columns at >=768px. |
| .a-cols-auto | Auto-fit card grid. | repeat(auto-fit, minmax(220px, 1fr)). |
| .a-span-2 | Expands an item across two columns. | Applies at >=768px. |
| .a-split | Two-panel layout primitive. | Becomes two equal columns at >=768px. |
| .a-aside | Content + sidebar primitive. | Uses 1fr + 320px at >=768px. |
| .a-aside--reverse | Reversed content + sidebar primitive. | Flips .a-aside columns at >=768px. |
| .a-prose | Readable text measure and spacing. | max-width: 65ch + increased line-height. |
| .a-muted | Secondary text color. | Uses var(--muted). |
| .a-card | Terminal card surface. | Border + padding + transparent background. |
| .a-panel | Larger panel surface. | Same visual treatment as .a-card. |
| .a-btn | Base button style. | Border-only terminal button. |
| .a-btn--primary | Emphasized button style. | Filled with foreground color. |
| .a-btn--ghost | Secondary button style. | Transparent button with border. |
| .a-badge | Compact badge/pill style. | Border + small padding. |
<main class="a-container a-stack a-gap-3">
<section class="a-section a-split a-gap-3">
<div class="a-stack a-gap-2">
<h1>Ship faster with AsciiTheme</h1>
<p class="a-prose a-muted">Base preset gives you typography, layout, and controls with one stylesheet.</p>
<div class="a-cluster a-gap-2">
<a class="a-btn--primary" href="#">Start</a>
<a class="a-btn--ghost" href="#">Read docs</a>
</div>
</div>
<aside class="a-panel">Sidebar panel</aside>
</section>
<section class="a-section a-grid a-cols-3 a-gap-2">
<article class="a-card">Feature A</article>
<article class="a-card">Feature B</article>
<article class="a-card">Feature C</article>
</section>
</main>Markup conventions
- Style axis is applied to root by the plugin:
data-style="default|ascii"
- Stickers:
[data-ascii-sticker="TEXT"]
- Optional role hooks for terminal components:
[data-ascii-role="cta"][data-ascii-role="card"][data-ascii-role="nav"][data-ascii-role="badge"]
Public API
initAsciiTheme(options?)(base: trueenables ASCII-only base preset)setAsciiStyle(style: "default" | "ascii")toggleAsciiStyle()getAsciiStyle(): "default" | "ascii"setAsciiMode(mode: "light" | "dark")toggleAsciiMode()renderAsciiStickers(root?: ParentNode)
Notes / limitations
- Contrast defaults in ASCII mode are tuned for utility-heavy sites (Tailwind-like
text-*/bg-*classes), so text and controls remain readable in both light and dark. - Card/panel classes (
.card,.card-gloss, and commoncard-*/panel-*variants) are normalized in managed dark and ASCII modes to prevent white-card regressions on dark backgrounds. - If your host theme uses a root
.dark/.lightclass (instead ofdata-theme), ASCII palette detection is supported out of the box. - This is not a full DOM-to-ASCII renderer.
- It focuses on a scoped theme layer and simple ASCII widgets (stickers + terminal component hooks).
- CSS is scoped to
:root[data-style="ascii"]to avoid host-site breakage. - Media is not restyled by default in ASCII mode (
img/video/avatar/logoremain unchanged unless you style them explicitly).
Demo
npm install
npm run devOpen the URL shown by Vite.
Live demo (GitHub Pages): https://markoblogo.github.io/AsciiTheme/
In the wild:
- You can also see this theme in the wild on the AGENTS.md generator landing: https://agentsmd.abvx.xyz/
- You can also see this theme on go.abvx.xyz: https://go.abvx.xyz/
- You can also see this theme on abvx.xyz: https://abvx.xyz/
- You can also see this theme on trade-solution.eu: https://trade-solution.eu/
- First-time setup: in GitHub repository settings, set Pages -> Source to GitHub Actions once, then rerun the Pages workflow.
To build the package:
npm run buildRelease checklist (v0.1.0)
Before tagging:
git status --short
npm ci
npm run build
npm run demo:buildCreate and push release tag:
# version is already 0.1.0 in package.json
git add -A
git commit -m "chore: release v0.1.0"
git tag v0.1.0
git push origin main --tagsManual npm publish (requires ownership and credentials):
npm login
npm publish --access publicIf package is unscoped, use:
npm publish