@djtobia/neobrutal-vue

Neo-brutalist Vue 3 component library with light/dark/custom themes, a 12 column grid and WCAG 2.2 AA accessibility
597
Install
npm install @djtobia/neobrutal-vue
Latest Version:0.2.1
Vue:3
License:MIT
Last Updated:Oct 9, 2026
Links: npm
Maintainer: djtobia

@djtobia/neobrutal-vue

Neo-brutalist component library for Vue 3, themed with the Eugene Elite Dodgeball palette.

Install

npm install @djtobia/neobrutal-vue vue

Requires Vue 3.5 or newer.

Use

import { createApp } from 'vue'
import NeoBrutalVue, { useTheme } from '@djtobia/neobrutal-vue'
import '@djtobia/neobrutal-vue/style.css'

// Fonts are not bundled. The theme font is Roboto:
import '@fontsource/roboto/400.css'
import '@fontsource/roboto/700.css'
import '@fontsource/roboto/900.css'

createApp(App).use(NeoBrutalVue).mount('#app')
useTheme().init() // restores saved theme ('light' | 'dark' | 'candy')

Icons (Material Symbols)

NbIcon (and NbThemeToggle, NbNav item icons) render Material Symbols by name, so the font must be loaded. The full variable font is about 4 MB, so load only the icons you use from Google Fonts instead, a few kilobytes:

<link
  rel="stylesheet"
  href="https://fonts.googleapis.com/css2?family=Material+Symbols+Outlined:opsz,wght,FILL,GRAD@24,700,0..1,0&icon_names=dark_mode,light_mode,menu&display=block"
/>

Add each icon name you use to icon_names. (Or import 'material-symbols/outlined.css' from the material-symbols package if you prefer self-hosting the whole font.)

Components can also be imported individually: import { NbButton } from '@djtobia/neobrutal-vue'.

Components

NbButton NbCard NbInput NbTextarea NbCheckbox NbBadge NbAlert NbModal NbDrawer NbHeader NbFooter NbSidebar NbNav NbThemeToggle NbIcon NbImg NbHero NbTable NbDataTable NbSpinner NbContainer NbRow NbCol NbSpacer

Using with vue-router

NbButton and NbNav render real links. To keep navigation client-side, pass your router's link component instead of relying on href:

<script setup>
import { RouterLink } from 'vue-router'
const items = [
  { key: '/', label: 'Home', to: '/' },
  { key: '/about', label: 'About', to: '/about' }
]
</script>

<template>
  <NbNav :items="items" :link-component="RouterLink" :model-value="$route.path" />
  <NbButton :as="RouterLink" to="/contact">Contact us</NbButton>
</template>

Mobile navigation

NbDrawer is a modal side panel (focus trapped, Escape closes, focus returns to the opener). Pair it with NbNav variant="list" and show the hamburger below a breakpoint using the opt-in utilities:

<NbNav class="d-none d-md-flex" :items="items" />
<NbButton class="d-md-none" aria-label="Open menu" @click="open = true">Menu</NbButton>
<NbDrawer v-model="open" title="Menu">
  <NbNav variant="list" :items="items" />
</NbDrawer>

Layout

A 12 column flex grid, in the style of Bootstrap and Vuetify:

<NbContainer>
  <NbRow>
    <NbCol cols="12" md="8">Main</NbCol>
    <NbCol cols="12" md="4">Sidebar</NbCol>
  </NbRow>
</NbContainer>
Component Props
NbContainer fluid (no max-width), tag
NbRow dense (0.5rem gutters), no-gutters, align (start center end stretch baseline), justify (start center end space-between space-around space-evenly), tag
NbCol cols, sm, md, lg, xl (1-12, auto, or true for equal width); offset, offset-sm...; order, order-sm... (0-12, first, last); align-self; tag
NbSpacer grows to push siblings apart inside a flex container

Breakpoints match Vuetify: sm 600px, md 960px, lg 1280px, xl 1920px (min-width). The gutter is --nb-gutter (default 1.5rem). The same classes work without Vue: nb-row, nb-col-md-6, nb-offset-2, nb-order-last, ...

Utility classes (opt in)

import '@djtobia/neobrutal-vue/utilities.css'

Vuetify-style names, all !important so they win over component styles:

Group Classes
Spacing (0-12, 1 unit = 0.25rem) ma-4 px-2 mt-auto pa-0 ... (m/p + a t r b l x y s e), gaps ga-4 gr-2 gc-2
Display (+ sm md lg xl) d-none d-flex d-md-none d-md-block d-grid ...
Flex flex-column flex-md-row flex-wrap flex-grow-1 justify-center justify-space-between align-center align-self-end
Typography text-h1..text-h6 text-body-1 text-body-2 text-caption text-overline text-center text-uppercase text-truncate font-weight-bold
Sizing and position w-100 h-50 fill-height min-h-screen position-relative overflow-hidden
Surfaces bg-primary bg-accent bg-surface ... (text color is set for contrast), border border-t rounded-lg rounded-pill elevation-0..elevation-4

These are kept out of the main stylesheet so they can never clash with another utility framework. The CSS is generated: edit scripts/generate-css.mjs and run npm run generate:css.

Theming

Everything reads CSS custom properties (--nb-primary, --nb-line, --nb-radius, ...) defined in src/styles/tokens.css. Set data-theme on <html> or any wrapper to switch themes.

Custom themes

import { registerTheme, useTheme } from '@djtobia/neobrutal-vue'

registerTheme('sunset', {
  extends: 'dark',          // optional: 'light' (default) | 'dark' | 'candy'
  primary: '#ff7a1a',
  accent: '#7b2cbf',
  radius: 8,                // numbers are px
  shadowOffset: 6
})

useTheme().setTheme('sunset')
  • Only the tokens you set are overridden; the rest come from extends.
  • Text colors on primary (ink), accent (onAccent) and danger (onDanger) are picked automatically for contrast when you don't set them and the color is a hex value.
  • Tokens: bg fg surface line ink primary secondary accent success danger onAccent onDanger heading borderWidth radius shadowOffset font.
  • registerTheme(name, def, { persist: true }) saves it to localStorage; useTheme().init() restores it. Call unregisterTheme(name) to remove one.
  • themeToCss(name, def) returns the generated CSS if you'd rather ship a stylesheet.
  • The Theming / Theme Builder story lets you pick colors live and copy the result.
  • checkThemeContrast(def) returns the token pairs that miss WCAG 2.2 AA (4.5:1 text, 3:1 for large headings and borders). The Theme Builder shows these live.

Accessibility

The library targets WCAG 2.2 Level AA. All three built-in themes pass automated axe-core checks (wcag2a, wcag2aa, wcag21a, wcag21aa, wcag22aa) across every story.

What the components do for you:

  • Contrast: text on every fill meets 4.5:1 in all built-in themes; custom NbCard / NbBadge hex colors and custom-theme text colors get readable text automatically.
  • Keyboard: everything is operable with a keyboard, with a visible 3px focus outline. Sortable headers are buttons; NbDataTable rows become focusable and respond to Enter/Space when you listen for row-click.
  • Modal: role="dialog", aria-modal, named by its title (or label), moves focus in, traps Tab, closes on Escape, restores focus and locks page scroll.
  • Forms: NbInput links label, hint and error (aria-describedby, aria-invalid), marks required fields, and passes autocomplete and other attributes to the <input>.
  • State is never color only: the current nav/sidebar item is underlined and has aria-current="page"; errors have an icon and text.
  • Status messages: NbAlert uses role="alert" for danger/warning and role="status" otherwise; NbSpinner is a status; NbDataTable announces its result range.
  • Motion: hover/press movement is disabled and spinners stop under prefers-reduced-motion.
  • Reflow and size: layouts use rem and wrap at narrow widths; interactive targets are at least 24px.

What is up to you:

  • Give content images a meaningful alt (NbImg defaults to "", i.e. decorative).
  • Keep one h1 per page (NbHero has heading-level), and give each NbNav / NbSidebar a unique label.
  • Text you place over an NbImg or NbHero photo: NbHero darkens the image, but check your own overlays.
  • A sticky NbHeader can cover focused elements; add scroll-padding-top matching its height.
  • Name tables with caption (NbDataTable) or a <caption> in NbTable.

Develop

npm install
npm run storybook   # component explorer on :6006
npm test            # unit tests (vitest + jsdom)
npm run build       # type-check + library build into dist/

Stories live in stories/ and tests in tests/; neither is part of the published package.

Related Packages

@el-agala/ui

Agala UI — A zero-dependency Vue 3 component library with atomic design tokens,...

6,676
@agala-labs/ui

Agala Labs UI — accessible Vue 3 components, semantic design tokens, and theming...

2,866 0
@hina-ui/vue

The design system behind Hikarinagi: Vue 3 components built on Tailwind CSS v4.

4,608 10
@orvain/vue

Orvain UI free components for Vue

406
@h0nio/ui

Official Vue 3 component library for H0N World, available for general use.

1,507 0

Version History

Version Released Vue License
0.2.1 ^3.5.0 MIT
0.2.0 ^3.5.0 MIT
0.1.0 ^3.5.0 MIT
Pre-releases (1)
Version Released License
0.0.0-stage