gamifiedui-css
v0.4.0
Published
Tailwind CSS v4 game UI: pixel-framed 2.5D buttons, panels, HP/MP/XP bars, dialogs, sprite and isometric utilities.
Maintainers
Readme
gamifiedui-css
Game-style 2D / 2.5D components for Tailwind CSS v4. CSS only, works with any framework.
npm i gamifiedui-css tailwindcss @tailwindcss/vite
npm i @fontsource/press-start-2p # optional pixel font
npm i @fontsource/fusion-pixel-12px-proportional-tc # optional, Chinese (Traditional)
npm i @fontsource/fusion-pixel-12px-proportional-sc # optional, Chinese (Simplified)/* app.css */
@import "tailwindcss";
@import "gamifiedui-css";The stylesheet is a Tailwind v4 entry point, so Tailwind must process it (Vite or PostCSS
plugin). Without that the build succeeds but @theme and @utility stay unprocessed.
CDN (no build step)
dist/ holds prebuilt stylesheets that need no Tailwind:
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@fontsource/press-start-2p/index.css">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/[email protected]/dist/gamifiedui.min.css">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/[email protected]/src/all.css">| File | Contains |
|---|---|
| dist/gamifiedui.min.css | Tailwind preflight (reset), every token, all components and utilities |
| dist/gamifiedui-core.min.css | the same without preflight, for pages that bring their own reset (use this one on an existing site, including Tailwind v3 apps, so its headings, links and tables keep their look) |
Both have an unminified .css twin, and unpkg.com/gamifiedui-css serves the same files.
Before the npm release, or to pin a commit, use jsDelivr's GitHub mirror:
https://cdn.jsdelivr.net/gh/carsonhung/gamifiedUI@<tag>/packages/gamifiedui-css/dist/gamifiedui.min.css.
The prebuilt files can't generate arbitrary values. They include gx-depth-0 to -8,
gx-sprite-{w,h}-{8,12,16,24,32,48,64,96,128}, gx-sprite-frames-1 to -16,
gx-sprite-scale-1 to -8, gx-sprite-ms-100 to -2000 (in steps of 100) and sr-only.
Any other value can be set inline: style="--gx-sprite-frames: 24". For Chinese, also load
@fontsource/fusion-pixel-12px-proportional-tc (or -sc) the same way.
Components
| Class | Markup | Modifiers |
|---|---|---|
| gx-btn | <button> | gx-btn-{primary,danger,success,warning,ghost}, gx-btn-{sm,lg}, aria-pressed="true", gx-btn-tint + --gx-btn-tint, gx-btn-tile (gx-btn-art, gx-btn-label, gx-btn-meta), gx-btn-tile-stacked |
| gx-panel | any block | gx-panel-title, gx-panel-flat |
| gx-bar | role="meter" + style="--gx-value: 0.6" | gx-bar-{hp,mp,xp}, gx-bar-smooth, gx-bar-critical |
| gx-badge | <span> | gx-badge-{primary,danger,success} |
| gx-dialog | <dialog> opened with showModal() | gx-dialog-{scene,text,actions} |
| gx-textbox | dialogue box | gx-textbox-{speaker,text,next,choices} |
| gx-alert | block, role="alert" only if urgent | gx-alert-{info,success,warning,danger}, parts -icon, -title, -body |
| gx-toast | inside one gx-toast-stack region; role="status" / "alert" | same tones + gx-toast-achievement, parts -icon, -title, -body, -close; data-leaving plays the exit |
| gx-spinner | inside role="status" with text | gx-spinner-lg |
| gx-tooltip | wrapper; content role="tooltip" linked by aria-describedby | gx-tooltip-bottom |
| gx-tabs / gx-tab | role="tablist" / role="tab" + aria-selected | panel: gx-panel gx-tab-panel |
| gx-field, gx-input | <input>, <textarea>, <select> | gx-field-{label,hint,error}, aria-invalid="true" |
| gx-checkbox, gx-radio, gx-switch | native inputs (role="switch" for the switch) in label.gx-choice | |
| gx-card | block, or <button> / <a> to make it pressable | gx-card-{title,meta}, gx-rarity-* |
| gx-slot | inventory cell, <button aria-pressed> | gx-slot-count, gx-rarity-* |
| gx-card-grid | <ul> of cards (or cards as children); columns fill automatically and cards in a row share a height | gx-card-grid-{sm,lg}, --gx-card-grid-min, --gx-card-grid-gap |
| gx-inventory | recessed tray of gx-slot buttons, role="group" with aria-label; fixed columns that shrink to fit narrow screens, down to a 44px touch target, then the tray scrolls sideways; aria-disabled marks locked cells | --gx-inventory-cols (set fewer on phones, e.g. [--gx-inventory-cols:4] sm:[--gx-inventory-cols:6]), --gx-inventory-slot, --gx-inventory-slot-min |
| gx-kbd | <kbd> key prompt | |
| gx-rarity-* | modifier for cards and slots | common, rare, epic, legendary (also print the name) |
| gx-frame | <figure> around <img>, <picture>, <canvas> or <video> | gx-frame-caption, gx-frame-pixelated, gx-frame-cover + gx-frame-{square,wide,portrait}, gx-rarity-* |
| gx-avatar | <span> with an <img alt>, or initials + role="img" aria-label | gx-avatar-{sm,lg}, gx-rarity-* |
| gx-video | gx-frame gx-video > gx-video-screen > <video controls> with <track kind="captions"> | gx-video-play start overlay (a real <button>) |
| gx-menu / gx-menu-item | <ul> of <button>s or links; blinking ▶ cursor on hover, focus and aria-current | gx-menu-hint |
| gx-dropdown | <details> > <summary> + gx-dropdown-panel | gx-dropdown-end |
| gx-accordion | <details> (share a name for one-open-at-a-time) | gx-accordion-meta, gx-accordion-body |
| gx-slider | <input type="range">; script sets --gx-value (0..1) for the WebKit fill | gx-slider-{hp,mp,xp} |
| gx-table | <table> in gx-table-wrap; tr[aria-current] highlights a row | gx-table-num, gx-rank + gx-rank-{1,2,3} |
| gx-pagination / gx-page | <ul> of buttons or links, aria-current="page" | gx-breadcrumb (<nav><ol>) |
| gx-drawer | <dialog> opened with showModal() | gx-drawer-start |
| gx-stepper / gx-step | <ol>; data-state="cleared\|current\|locked" | gx-step-node |
| gx-chip | <span>, or <button aria-pressed> for a filter | tones as buttons |
| gx-divider | <hr> | gx-divider-text (a labelled divider) |
| gx-skeleton | placeholder inside aria-busy="true" | gx-skeleton-{text,avatar,block}, --gx-skeleton-w |
| gx-rating | role="img" + aria-label, style="--gx-value: 3" | gx-rating-stars, gx-rating-{sm,lg}, --gx-rating-max |
| gx-counter | icon + gx-counter-value; data-bump plays a pop | gx-counter-{xp,hp} |
| gx-quantity | − N + stepper: two gx-quantity-btn around a number gx-quantity-value; aria-disabled sinks a button at a limit, data-bump pops the value | --gx-quantity-digits |
| gx-reward | gx-dialog gx-reward with gx-reward-item (spinning rays) | gx-reward-{title,name} |
| gx-gauge | timing gauge: gx-gauge-zone (with gx-gauge-sweet) and gx-gauge-marker; gx-gauge-vertical with gx-gauge-fill for hold-to-fill | --gx-value, --gx-zone-start, --gx-zone-size, --gx-sweet-start, --gx-sweet-size (0..1) |
| gx-lane | rhythm lane: gx-lane-line (the hit line) and gx-lane-note (data-done hides a hit note) | --gx-at on a note (0 on the line, 1 at the far end), --gx-lane-hit |
| gx-dpad | four gx-btn in a cross: gx-dpad-{up,right,down,left}; data-lit lights one even while disabled | |
| gx-btn-hold | press-and-hold button without the phone long-press menu; pair with holdButton() | |
| gx-flip | <li> in a gx-flip-row holding gx-flip-back (aria-hidden) and gx-flip-face; turns over once when inserted | --gx-flip-delay (stagger), --gx-flip-w, --gx-flip-h, gx-rarity-* |
| gx-quest / gx-objective | HUD quest tracker; data-done ticks an objective | gx-quest-{title,list}, gx-objective-{text,count} |
| gx-skill | <button>; --gx-cooldown (1..0) sweeps, aria-disabled while cooling | gx-skill-key, gx-skill-timer, data-ready |
| gx-float | short text in a gx-float-layer; rises and fades once | gx-float-{damage,heal,xp,crit} |
| gx-timer | role="timer" with gx-timer-label + <time class="gx-timer-value">; --gx-value (1..0) drains the strip | data-state="warning\|critical\|done", gx-timer-lg |
| gx-calendar | month grid of <button class="gx-day">; rendered by calendar() in gamifiedui-utils | day: aria-current="date", aria-pressed, data-range="start\|middle\|end\|preview", data-outside, aria-disabled, data-event + gx-rarity-*, data-claimed |
| gx-datepicker | .gx-input + gx-datepicker-toggle + [popover].gx-datepicker-panel holding a gx-calendar | single date or a range; anchored under the field where CSS anchor positioning exists |
| gx-time | scrolling list (role="listbox") of gx-time-option; rendered by timeList() in gamifiedui-utils | option: aria-selected, data-range="start\|middle\|end\|preview", aria-disabled, gx-time-tag (+1 for next day); gx-time-title, gx-time-summary, --gx-time-rows |
| gx-timepicker | .gx-input + gx-timepicker-toggle + [popover].gx-timepicker-panel holding a gx-time | single time or a range (overnight allowed); same anchoring as gx-datepicker |
| gx-dropzone | <label> wrapping <input type="file"> (click and keyboard work without JS) | gx-dropzone-{title,hint}, data-dragging, data-invalid |
| gx-file-list / gx-file | <ul> of uploads | gx-file-{name,size,remove,status}, gx-bar gx-file-progress, data-state="uploading\|done\|error" |
| gx-icon | <span aria-hidden="true"> + an icon class from gamifiedui-icons | gx-icon-{sm,lg,xl} (16/48/64px; default 32), tones, gx-icon-shadow, gx-icon-tile |
Particles
- Bursts: a
gx-burstinside any positioned element, holdinggx-particlechildren. Each child gets--gx-dx,--gx-dy(in rem),--gx-rotand--gx-delay. Colours aregx-particle-{coin,heart,xp,petal,rarity}andgx-particle-lg.gx-particle-confetticycles five theme colours by position and falls further; addgx-particle-ribbonfor a pixel zigzag streamer. Add an icon class such asgx-i-cointo use that shape.burst()ingamifiedui-utilsbuilds all of this for you. - Ambient:
<div class="gx-ambient gx-ambient-snow" aria-hidden="true">with 12 empty<i>, placed inside a positioned element. It needs no script. Kinds aresnow,rain,embers,petalsandconfetti(tumbling pieces with a streamer every fourth).
Both are hidden under reduced motion, since they are decoration.
JavaScript
Every component works without script. A few effects only make sense when driven, so
gamifiedui-utils ships small optional helpers for them: toasts,
typewriter, number roll, skill cooldown, floating text, bursts, tabs, the slider fill, timers,
calendars and date (range) pickers, and drag-and-drop upload.
A file button needs no script: <label class="gx-btn">Choose file<input type="file" class="sr-only"></label>.
Any colour for a button, with hover and a readable black or white label worked out for you:
<button class="gx-btn gx-btn-tint" style="--gx-btn-tint: #8a3ab9">Epic</button>Item, shop or level buttons with a picture (an <img>, <svg> or gx-icon):
<button class="gx-btn gx-btn-tile gx-btn-tint" style="--gx-btn-tint: #3a6b4a">
<span class="gx-btn-art"><img src="potion.png" alt=""></span>
<span class="gx-btn-label">Green potion</span>
<span class="gx-btn-meta">+20 HP</span>
</button>gx-btn-tile-stacked puts the picture on top as a postcard (--gx-btn-art-ratio, default
16 / 9); an optional gx-icon after it sits beside the label. --gx-btn-art-size sets the side
picture (default 2.5rem), and --gx-btn-tint-fg overrides the automatic label colour.
Utilities
gx-depth-{n}: depth of raised components, in pixel units (--gx-px, default 4px).gx-pixel-frame,gx-pixelated.gx-iso: true isometric camera (orthographic). Pair children with Tailwindtransform-3dandtranslate-z-*.gx-spritewithgx-sprite-w-16 gx-sprite-frames-8 gx-sprite-scale-4 gx-sprite-ms-800andbg-[url(/hero.png)]: horizontal sprite strip played withsteps().
Themes and motion
Set data-gx-theme="vintage" or "glass" on any element (themes nest); no attribute means
arcade. Override any --color-gx-* token to make your own. vintage is a classic Windows PC
game desktop: white window frames, thin grey borders, soft shadows, lightly rounded tiles, a
light blue taskbar tone and sentence-case labels in the system UI face.
glass is a modern, XR-style spatial look rather than a pixel one: lightly translucent flat
surfaces with a thin light rim, soft depth, no bevels or gradients, pill controls and
sentence-case labels in --font-gx-body. It is built on shape hooks any theme can set, each
defaulting to the square pixel look: --gx-radius, --gx-radius-control, --gx-backdrop,
--gx-radius-field, --gx-radius-check, --gx-elevation, --gx-case, --color-gx-frame,
--gx-line, --gx-focus-width,
--gx-bevel, --gx-sheen, --gx-bar-height, --gx-bar-texture and --gx-bar-easing.
--gx-px stays the sizing grid, so thinner lines never shrink icons or spacing. Every animation stops under
prefers-reduced-motion: reduce or inside data-gx-motion="reduce" (for an in-app setting).
Chinese, Japanese and Korean
Press Start 2P only covers Latin, so --font-gx falls back to Fusion Pixel 12px (Traditional
Chinese by default). Set lang on <html> or any element:
lang="zh-Hant"/zh-TW/zh-HK: Traditional glyphs.lang="zh-Hans"/zh-CN/zh-SG: Simplified glyphs.- Any
zh,jaorko: pixel-font sizes snap to 12px and 24px (the font's grid, so glyphs stay crisp) andline-break: strictkeeps punctuation off line starts.
Each Fusion Pixel file is about 600 KB, but browsers only fetch it once a CJK character is on
screen. For typewriter effects, split text with Intl.Segmenter (graphemes), not slice.
License
Apache License 2.0. Free for commercial use, with an explicit patent grant. See NOTICE.
