Bold 3px borders. Hard offset shadows. Uppercase everything. Squared corners. Zero blur, zero apologies.
Works with React · Vue 3 · Svelte · Pure CSS · Tailwind CSS
// Import the CSS once in your entry file import '@mariojgt/neo-css/dist/neo.css' // React — named imports, fully tree-shakeable import { NeoButton, NeoCard, NeoAlert, NeoTag } from '@mariojgt/neo-react' // Vue 3 — global plugin or individual imports import NeoVue from '@mariojgt/neo-vue' createApp(App).use(NeoVue).mount('#app') // Svelte — individual component imports import { NeoButton, NeoCard } from '@mariojgt/neo-svelte' // Tailwind preset // tailwind.config.js module.exports = { presets: [require('@mariojgt/neo-css/tailwind-preset')] }
/* CSS */ <button class="neo-btn neo-btn-primary neo-btn-md">Primary</button> // React <NeoButton variant="primary" size="md">Primary</NeoButton> // Vue <NeoButton variant="accent" size="lg">Accent</NeoButton> // Svelte <NeoButton variant="ghost" on:click={handler}>Ghost</NeoButton>
<span class="neo-tag neo-tag-accent neo-tag-rotate">Hot</span> <span class="neo-badge neo-badge-success">Published</span> // React <NeoTag variant="accent" rotated>Hot</NeoTag> <NeoBadge variant="success">Published</NeoBadge>
Standard card. 3px border, 5px shadow. Hover to lift.
Green top accent bar.
Adds a ★ in the top-right corner.
Micro-grid pattern on hover.
.neo-stat-card
12,847
+23% this month
<div class="neo-card">Standard</div> <div class="neo-card-brand">Brand</div> <div class="neo-card neo-star">Starred</div> // React <NeoCard variant="brand" pattern>Content</NeoCard> <NeoCard variant="stat">12,847</NeoCard>
/* CSS classes */ <input class="neo-input" /> <select class="neo-select">...</select> <textarea class="neo-input"></textarea> // React <NeoInput label="Name" placeholder="Your name..." /> <NeoTextarea label="Bio" placeholder="About you..." /> <NeoSelect label="Level" :options="opts" />
| Course | Students | Rating | Status |
|---|---|---|---|
| React Mastery | 2,431 | ★ 4.9 | Published |
| Node.js Deep Dive | 1,847 | ★ 4.7 | Published |
| TypeScript Pro | 963 | ★ 4.8 | Draft |
/* CSS */ <table class="neo-table"> <thead><tr><th>Course</th>...</tr></thead> <tbody><tr><td>React Mastery</td>...</tr></tbody> </table> // React — generic typed component <NeoTable rowKey="id" columns={[{ key: 'name', header: 'Course' }]} data={courses} />
// React const [view, setView] = React.useState('grid') <NeoToggleGroup value={view} onChange={setView}> <NeoToggleItem value="grid">Grid</NeoToggleItem> <NeoToggleItem value="list">List</NeoToggleItem> </NeoToggleGroup> <NeoTabBar value={tab} onChange={setTab}> <NeoTab value="details">Details</NeoTab> <NeoTab value="media">Media</NeoTab> </NeoTabBar>
Success — Your changes have been saved.
Warning — Your subscription expires in 3 days.
Error — Something went wrong. Please try again.
Info — New courses drop every week.
<div class="neo-alert neo-alert-success">...</div> // React <NeoAlert variant="success" title="Saved" icon={<CheckIcon/>}> Your changes have been saved. </NeoAlert> // Vue <NeoAlert variant="warning" title="Heads up"> <template #icon><WarningIcon/></template> Expires soon. </NeoAlert>
Dashed divider with scissors:
Content below the cut.
.neo-divider
Price Display
$49.99.neo-price
Gradient Text
Emerald Gradient
.neo-text-gradient
Fade In Up
.animate-neo-fade-in-up
Float
.animate-neo-float
Ping Slow
.animate-neo-ping-slow
Gradient Shift
.animate-neo-gradient-shift
<div class="animate-neo-fade-in-up">Fades in on load</div> <span class="animate-neo-float">★</span> <span class="animate-neo-ping-slow"></span> <h1 class="animate-neo-gradient-shift">Gradient</h1>
// Drop-in vanilla JS helper — zero dependencies import { neoToast } from '@mariojgt/neo-css/toast' // neoToast(variant, title, message, duration?) neoToast('success', 'Saved!', 'Changes saved.') // 4 s auto-dismiss neoToast('warning', 'Heads Up', 'Expires soon.') // 4 s auto-dismiss neoToast('error', 'Error', 'Try again.') // 4 s auto-dismiss neoToast('info', 'Tip', 'Pro tip here.') // 4 s auto-dismiss neoToast('success', 'Sticky', 'No dismiss.', 0) // duration 0 = stays // React — tiny wrapper hook const { toast } = useNeoToast() toast.success('Saved!', 'All good.') toast.error('Oops', 'Try again.') // Vue — composable const { toast } = useNeoToast() toast.warning('Watch out', 'Expires soon.')
Override CSS custom properties on :root to switch the entire palette instantly. Pick one below — every component on this page updates live.
Emerald
The default. Deep teal brand with amber accent.
Violet
Electric purple brand with hot-pink accent.
Crimson
Bold red brand with golden-orange accent.
// JS — zero dependencies, works everywhere setNeoTheme('emerald') // default setNeoTheme('violet') setNeoTheme('crimson') // Or supply your own token overrides setNeoTheme({ '--neo-brand': '#0ea5e9', '--neo-brand-light': '#38bdf8', '--neo-brand-dark': '#0284c7' }) /* CSS — override tokens directly in your stylesheet */ :root { --neo-brand: #7c3aed; --neo-brand-light: #a855f7; --neo-brand-dark: #6d28d9; --neo-accent: #ec4899; --neo-accent-dark: #db2777; }
Tweak the tokens below — every component on this page updates in real time. Hit Copy CSS to grab the override block.
Brand Color
Accent Color
Surfaces
Live Preview
Sample Card
Border top picks up your brand color.
Generated CSS
Available on npm — fully typed, tree-shakeable, zero extra dependencies beyond the CSS theme.
The building blocks that put this kit on the same level as shadcn, DaisyUI, and Radix.
<span class="neo-kbd">⌘ K</span> <span class="neo-kbd">⌃ ⇧ P</span> <span class="neo-kbd">Esc</span> // React import { NeoKbd } from '@mariojgt/neo-react' <NeoKbd>⌘ K</NeoKbd> // Vue / Svelte (same API) import { NeoKbd } from '@mariojgt/neo-vue' <NeoKbd>⌘ K</NeoKbd>
<!-- Pure CSS — add data-tip to any element --> <button class="neo-btn neo-btn-primary" data-tip="Creates a new project"> New Project </button> // React import { NeoTooltip, NeoButton } from '@mariojgt/neo-react' <NeoTooltip tip="Creates a new project"> <NeoButton>New Project</NeoButton> </NeoTooltip> // Vue <NeoTooltip tip="Creates a new project"> <NeoButton>New Project</NeoButton> </NeoTooltip> // Svelte import { NeoTooltip } from '@mariojgt/neo-svelte' <NeoTooltip tip="Creates a new project"> <NeoButton>New Project</NeoButton> </NeoTooltip>
<!-- Shimmer skeleton — pure CSS --> <div class="neo-skeleton neo-skeleton-thumb"></div> <div class="neo-skeleton neo-skeleton-title"></div> <div class="neo-skeleton neo-skeleton-text"></div> <div class="neo-skeleton neo-skeleton-avatar"></div> <div class="neo-skeleton neo-skeleton-btn"></div> // React import { NeoSkeleton } from '@mariojgt/neo-react' <NeoSkeleton variant="thumb" /> <NeoSkeleton variant="title" /> <NeoSkeleton variant="text" /> <NeoSkeleton variant="avatar"/> <NeoSkeleton variant="btn" /> // Vue / Svelte — same API <NeoSkeleton variant="thumb" />
<div class="neo-avatar neo-avatar-md neo-avatar-brand neo-avatar-online">MJ</div> <!-- Group --> <div class="neo-avatar-group"> <div class="neo-avatar neo-avatar-md">MJ</div> <div class="neo-avatar neo-avatar-md">AB</div> <div class="neo-avatar neo-avatar-md">+4</div> </div> // React import { NeoAvatar, NeoAvatarGroup } from '@mariojgt/neo-react' <NeoAvatar size="md" color="brand" status="online">MJ</NeoAvatar> <NeoAvatarGroup> <NeoAvatar size="md">MJ</NeoAvatar> <NeoAvatar size="md">AB</NeoAvatar> <NeoAvatar size="md">+4</NeoAvatar> </NeoAvatarGroup> // Vue / Svelte — same props <NeoAvatar size="lg" color="accent">AB</NeoAvatar>
@mariojgt/neo-css/tailwind-preset) that maps all Neo design tokens to Tailwind utilities.
<div class="neo-accordion"> <div class="neo-accordion-item open"> <button class="neo-accordion-trigger" onclick="neoAccordion(this)"> Title <span class="neo-accordion-chevron">▼</span> </button> <div class="neo-accordion-body">Content here.</div> </div> </div> // React import { NeoAccordion } from '@mariojgt/neo-react' const items = [ { id: 'a', trigger: 'What is Neo UI?', content: 'A neo-brutalist design system.' }, { id: 'b', trigger: 'Free?', content: '100% MIT licensed.' }, ] <NeoAccordion items={items} defaultOpen={['a']} /> // Vue <NeoAccordion :items="items" :default-open="['a']" /> // Svelte import { NeoAccordion } from '@mariojgt/neo-svelte' <NeoAccordion items={items} defaultOpen={['a']} />
<!-- Trigger --> <button onclick="neoModalOpen('my-modal')">Open</button> <!-- Modal HTML --> <div class="neo-modal-backdrop" id="my-modal"> <div class="neo-modal"> <div class="neo-modal-header"> <h2 class="neo-modal-title">Title</h2> <button class="neo-modal-close" onclick="neoModalClose('my-modal')">✕</button> </div> <div class="neo-modal-body">Body text...</div> <div class="neo-modal-footer"> <button class="neo-btn neo-btn-primary">Confirm</button> </div> </div> </div> // JS neoModalOpen('my-modal') neoModalClose('my-modal') // React import { NeoModal, NeoButton } from '@mariojgt/neo-react' const [open, setOpen] = React.useState(false) <NeoModal open={open} title="Confirm Action" onClose={() => setOpen(false)}> Body text here. </NeoModal> // Vue <NeoModal :open="isOpen" title="Title" @close="isOpen = false"> Body text here. <template #footer> <NeoButton variant="primary">OK</NeoButton> </template> </NeoModal> // Svelte import { NeoModal } from '@mariojgt/neo-svelte' <NeoModal open={isOpen} title="Title" on:close={() => isOpen = false}> Body text here. </NeoModal>
// Open with JS or keyboard shortcut neoCommandOpen() // Register custom commands neoCommandRegister([ { icon: '🏠', label: 'Go Home', action: () => location.href = '/' }, { icon: '⚡', label: 'New Game', badge: 'hot', action: startGame }, { icon: '⚙', label: 'Settings', action: openSettings }, ]) // Keyboard shortcut built-in: ⌘K or Ctrl+K
Iron Sword
Common
Forest Shield
Uncommon
Arcane Orb
Rare
Soul Reaver
Epic
Crown of Ages
Legendary
<div class="neo-item-card neo-rarity neo-rarity-legendary"> <div class="neo-item-icon">👑</div> <p class="neo-item-name">Crown of Ages</p> <p class="neo-item-rarity">Legendary</p> </div> /* Variants: neo-rarity-common | uncommon | rare | epic | legendary */ // React import { NeoRarityCard } from '@mariojgt/neo-react' <NeoRarityCard name="Crown of Ages" rarity="legendary" icon="👑" /> // Vue <NeoRarityCard name="Arcane Orb" rarity="rare" icon="🔮" /> // Svelte import { NeoRarityCard } from '@mariojgt/neo-svelte' <NeoRarityCard name="Soul Reaver" rarity="epic" icon="🗡️" />
<div class="neo-leaderboard"> <div class="neo-lb-header">...header...</div> <div class="neo-lb-row"> <span class="neo-lb-rank neo-lb-rank-1">🥇</span> <span class="neo-lb-name">mariojgt</span> <span class="neo-lb-score">142,830</span> </div> </div> // React import { NeoLeaderboard } from '@mariojgt/neo-react' const entries = [ { rank: 1, name: 'mariojgt', score: '142,830' }, { rank: 2, name: 'darkwolf99', score: '98,410' }, { rank: 3, name: 'xkira', score: '87,200' }, ] <NeoLeaderboard entries={entries} /> // Vue <NeoLeaderboard :entries="entries" /> // Svelte import { NeoLeaderboard } from '@mariojgt/neo-svelte' <NeoLeaderboard entries={entries} />
Drop-in classes and JS helpers designed for game UIs, dashboards, and anything that needs some impact.
// JS — shake the whole page neoShake('sm') // subtle neoShake('md') // noticeable neoShake('lg') // earthquake neoShake('md', el) // shake a specific element /* CSS classes */ <div class="neo-shake-md">Shakes on mount</div>
<!-- data-text must match inner text --> <span class="neo-glitch" data-text="GAME OVER"> GAME OVER </span> // Toggle off el.classList.toggle('neo-glitch-pause')
<!-- HTML structure --> <div class="neo-xp-bar"> <div class="neo-xp-label"> <span class="neo-xp-label-text">⚡ XP</span> <span class="neo-xp-label-val">6,240 / 10,000</span> </div> <div class="neo-xp-track" data-striped> <div class="neo-xp-fill" style="width:62%"></div> </div> </div> // Variants "neo-xp-fill" // green XP "neo-xp-fill neo-hp-fill" // red HP "neo-xp-fill neo-mp-fill" // blue MP "neo-xp-track-sm" // slim variant "neo-xp-track-lg" // chunky variant // React — NeoProgressBar component import { NeoProgressBar } from '@mariojgt/neo-react' <NeoProgressBar variant="xp" value={62} label="⚡ XP" valueLabel="6,240 / 10,000" striped /> <NeoProgressBar variant="hp" value={68} label="♥ HP" /> <NeoProgressBar variant="mp" value={40} label="✦ MP" /> // Vue <NeoProgressBar variant="xp" :value="xp" label="⚡ XP" striped /> // Svelte import { NeoProgressBar } from '@mariojgt/neo-svelte' <NeoProgressBar variant="hp" value={hp} label="♥ HP" />
// neoAchievement(icon, title, description) neoAchievement('🏆', 'First Blood', 'You landed your first hit.') neoAchievement('⚡', 'Speedrunner', 'Under 5 min.') neoAchievement('💎', 'Legend', 'Rank S on all stages.')
// JS counter helpers neoComboHit() // increment + animate neoComboReset() // reset to 0 <!-- HTML --> <div class="neo-combo"> <span class="neo-combo-count">12</span> <span class="neo-combo-label">COMBO</span> </div>
Add .neo-scanlines to any element for a CRT effect.
// Particle burst at cursor position btn.addEventListener('click', e => neoParticles(e)) // Screen flash (hit feedback) neoFlash() // Typewriter neoTypewriter(element, 'INSERT COIN TO CONTINUE_', 55) /* CRT scanlines */ <div class="neo-scanlines">...</div>
Hold the cursor over the button — a power bar charges, then fires when full.
/* CSS charge bar */ <button class="neo-btn neo-btn-primary neo-btn-charge"> Hold to Charge </button> // JS — fire after 1.4 s hover startCharge(el) // bind to mouseenter cancelCharge(el) // bind to mouseleave
This is a fully keyboard-accessible modal with a backdrop blur, spring animation, and a close button. Click outside or press Esc to dismiss.
This action is irreversible. Are you sure you want to delete this item permanently?