@twentyfourg/grimoire

A modern, headless Vue 3 component library built with TypeScript and Composition API. Create your own design system with unstyled, accessible components.
7,265
Install
npm install @twentyfourg/grimoire
Latest Version:3.1.0
Vue:3
Older releases:2
Last Updated:Aug 27, 2026
Links: npm
Maintainer: briananstett

Grimoire v3 Version

A modern, headless Vue 3 component library built with TypeScript and Composition API

Grimoire is a collection of unstyled components that provide structure, accessibility, and logic while letting you control the styling. Perfect for building your own design system or theming components to match any brand.

✨ Key Features

  • 🎨 Headless Components - Unstyled by default, bring your own styles
  • ⚡ Vue 3 Composition API - Modern, performant, tree-shakeable
  • 🔷 TypeScript First - Full type safety and IntelliSense support
  • 🎭 Theme System - Create styled wrappers with useStyledComponent composable
  • ♿ Accessible - Built with accessibility in mind
  • 🎯 Direct Ref Access - No more @refMounted events needed!
  • 🧪 Fully Tested - Comprehensive unit and E2E test coverage
  • 🎨 Iconify Integration - Built-in support for 200,000+ icons

🌐 Browser Support

Supports all modern browsers (latest versions):

  • Chrome/Edge (Chromium)
  • Firefox
  • Safari
  • Opera

Note: IE11 is not supported.


📦 Installation & Setup

Install the Package

npm install @twentyfourg/grimoire
# or
pnpm add @twentyfourg/grimoire
# or
yarn add @twentyfourg/grimoire

Basic Setup

<script setup>
import { GButton, GInput, GModal } from '@twentyfourg/grimoire';
import { ref } from 'vue';

const value = ref('');
</script>

<template>
  <GButton variant="primary filled">Click Me</GButton>
  <GInput v-model="value" placeholder="Enter text" />
</template>

Setup with Icons (Optional)

Grimoire uses Iconify for icons. Register icon collections with the plugin:

// main.ts
import { createApp } from 'vue';
import Grimoire from '@twentyfourg/grimoire/plugin';
import { icons } from './icons-bundle'; // Your custom icons (see below)
import App from './App.vue';

const app = createApp(App);

app.use(Grimoire, {
  icons, // Your icon collections
  importBaseIcons: true, // Include Grimoire's base icons (default: true)
});

app.mount('#app');

Build your own icon bundles from SVG files:

// vite.config.ts
import { viteIconBundlePlugin } from '@twentyfourg/grimoire/build-tools';

export default defineConfig({
  plugins: [
    viteIconBundlePlugin({
      sources: [
        {
          type: 'folder',
          path: 'icons', // Your SVG folder
          prefix: 'app', // Icons will be: app:icon-name
          variants: 'both', // Creates colored & monotone versions
        },
      ],
    }),
  ],
});

Then use icons in components:

<template>
  <GIcon icon="app:logo" />
  <GButton icon="app:plus">Add Item</GButton>
  <GInput icon-left="app:search" placeholder="Search..." />
</template>

📚 See: Build Tools Documentation for advanced icon options


📦 Available Components

Component Description
GAvatar User avatar with loading/error states
GBreadcrumbs Breadcrumb navigation
GButton Button with icon support and variants
GCheckbox Checkbox with optional label and indeterminate state
GCollapsible Collapsible/accordion sections
GDatepicker Date and datetime picker (powered by @vuepic/vue-datepicker)
GDivider Visual divider
GDropdown Select/multiselect dropdown (powered by @vueform/multiselect)
GForm Form with validation support
GFormField Form field wrapper with label and error states
GFormFieldInput Complete form field with integrated input
GIcon Icon component (Iconify integration)
GInput Text input, textarea, number input with masking support
GModal Modal dialog with backdrop
GPagination Pagination controls
GProgressBar Linear progress bar
GProgressCircle Circular progress indicator
GRadio Radio button with optional label
GSegmentedControl Single-select control laying options out in a shared container
GSkeleton Loading skeleton placeholders
GSwitch Toggle switch
GTable Data table with sorting and pagination
GTabs Tab navigation with router support
GThumbnail File thumbnail previews

Component Documentation

📚 Documentation site is currently under construction. Complete API documentation for each component will include:

  • Prop references with types
  • Event documentation
  • Slot descriptions
  • HTML structure
  • CSS variables
  • Usage examples

In the meantime, you can view component source code in packages/grimoire/src/components for detailed prop definitions and TypeScript types.


🎨 Creating Styled Components

Grimoire components are intentionally unstyled. Create themed wrappers for your project using the useStyledComponent composable:

<!-- StyledButton.vue -->
<template>
  <GButton ref="buttonRef" v-bind="mergedProps" class="styled-button">
    <template v-for="(_, name) in $slots" v-slot:[name]="slotData">
      <slot :name="name" v-bind="slotData" />
    </template>
  </GButton>
</template>

<script setup lang="ts">
import { ref } from 'vue';
import { GButton, type GButtonExposed } from '@twentyfourg/grimoire';
import { useStyledComponent } from '@twentyfourg/grimoire/composables';

const props = defineProps({
  ...GButton.props,
  // Override base component defaults
  colorVariants: {
    type: Array,
    default: () => ['primary', 'danger'],
  },
  rounded: {
    type: Boolean,
    default: true, // Make rounded default for your project
  },
});

// ✅ Re-export the exposed type for TypeScript consumers
export type StyledButtonExposed = GButtonExposed;

const buttonRef = ref<GButtonExposed>();
const { mergedProps, expose } = useStyledComponent<GButtonExposed>(buttonRef, props);

// ✅ This ensures all GButton methods are available on your styled component
defineExpose(expose);
defineOptions({
  inheritAttrs: false,
});
</script>

<style scoped>
.styled-button {
  /* Your project's button styles */
  background: var(--color-primary);
  color: white;
  padding: 12px 24px;
  border-radius: 8px;
  border: none;
  cursor: pointer;
  transition: background 0.2s;
}

.styled-button:hover {
  background: var(--color-primary-dark);
}

.styled-button.rounded {
  border-radius: 24px;
}
</style>

Benefits of useStyledComponent

  • ✅ Automatic prop merging - spreads base props, merges overrides
  • ✅ Method forwarding - all base component methods automatically available
  • ✅ Direct ref access - no more @refMounted events
  • ✅ TypeScript safe - full type inference
  • ✅ Consistent pattern across all your styled components

Advanced: Conditional Props

For conditional logic based on other props, use computeProps:

const { mergedProps, expose } = useStyledComponent(dropdownRef, props, {
  computeProps(merged, attrs) {
    // Apply smart defaults for multi-select modes
    if (merged.mode === 'tags' || merged.mode === 'multiple') {
      merged.hideSelected = merged.hideSelected ?? false;
      merged.closeOnSelect = merged.closeOnSelect ?? false;
    }
    return merged;
  },
});

📚 See: Styled Component Migration Guide for complete details


🎯 Direct Ref Access (v3 Feature!)

One of v3's biggest improvements: access child component methods directly through template refs.

Old Way (v2)

<template>
  <StyledDropdown @refMounted="dropdownRef = $event" />
</template>

<script setup>
const dropdownRef = ref(null);

function openDropdown() {
  dropdownRef.value.open(); // Only works after @refMounted fires
}
</script>

New Way (v3)

<template>
  <StyledDropdown ref="dropdown" />
  <button @click="dropdown.open()">Open</button>
</template>

<script setup>
import { ref } from 'vue';

const dropdown = ref(null);

// All base component methods available directly!
function doStuff() {
  dropdown.value.open();
  dropdown.value.close();
  dropdown.value.toggle();
  dropdown.value.$base.value; // Escape hatch to child ref
}
</script>

No more:

  • ❌ @refMounted event listeners
  • ❌ Waiting for mounted lifecycle
  • ❌ Event handler boilerplate

Just:

  • ✅ Direct ref access like native Vue
  • ✅ All base component methods available
  • ✅ TypeScript autocomplete support

Exposed Types for Full TypeScript Support

Every Grimoire component exposes its methods and properties through an *Exposed type. This gives you complete TypeScript autocomplete and type safety:

import { ref } from 'vue';
import { type GDropdownExposed, type GInputExposed } from '@twentyfourg/grimoire';

const dropdown = ref<GDropdownExposed>();
const input = ref<GInputExposed>();

// ✅ TypeScript knows ALL available methods and properties!

// VueMultiselect methods (from wrapped component)
dropdown.value?.open();
dropdown.value?.close();

// Custom GDropdown properties
dropdown.value?.isDropdownOpen;
dropdown.value?.toggle();

// Native HTMLInputElement methods
input.value?.focus();
input.value?.blur();

// Custom GInput methods
input.value?.clear();
input.value?.validate();

When creating styled wrappers, you should re-export the base component's exposed type to maintain this TypeScript support. See the styled component example below for how to do this.


🔷 TypeScript Support

All components are written in TypeScript with full type definitions:

import type { GButtonProps } from '@twentyfourg/grimoire';

interface MyButtonProps extends GButtonProps {
  myCustomProp?: string;
}

const props = defineProps<MyButtonProps>();

For component ref types (to get full TypeScript autocomplete for methods and properties), see the Exposed Types section above.


🧪 Testing

Grimoire has comprehensive test coverage:

  • 40+ unit tests per component (Vitest)
  • E2E visual regression tests (Playwright)
  • 100% prop coverage
  • Accessibility testing

Run tests:

# Unit tests
pnpm test

# Unit tests with UI
pnpm test:ui

# E2E tests
pnpm --filter @grimoire/showcase test:e2e

# Coverage report
pnpm test:coverage

📚 See: Testing Strategy for testing styled components


📚 Documentation


🤝 Contributing

Contributions are welcome!

Development Setup

# Clone the repo
git clone https://github.com/twentyfourg/grimoire.git

# Install dependencies
pnpm install

# Run showcase in dev mode
pnpm run:showcase

# Run tests
pnpm test

Project Structure

grimoire/
├── @app/showcase/         # Component showcase app
├── packages/
│   ├── grimoire/          # Core unstyled components
│   │   └── ai-docs/       # Consumer documentation (shipped with npm)
│   └── themes/            # Styled component examples
├── dev-docs/              # Internal developer documentation
├── ai_docs/               # AI-assisted maintenance documentation
└── scripts/               # Build and utility scripts

Documentation

  • dev-docs/ - Internal documentation for Grimoire maintainers
  • ai_docs/ - AI-assisted docs for maintaining Grimoire (creating showcases, tests, etc.)
  • packages/grimoire/ai-docs/ - Consumer docs shipped with npm (migration guide, component usage, etc.)

📝 Changelog

See CHANGELOG.md for version history.

We follow Semantic Versioning.


📄 License

MIT License - see LICENSE file for details


Built with ❤️ by 24G

Related Packages

@hmfw/ant-design

基于 Ant Design v6 的 Vue3 组件库 —— 71 个高质量组件,完整 TypeScript 支持

10,269 1
@vuetify/v0

Headless, unstyled, accessible Vue 3 primitives and composables — a meta UI fram...

711,847 739
@moduix/vue

Accessible Vue UI components built on Ark UI and styled with CSS Modules. 80+ co...

425 29
@waler/ui

Design system Vue 3 da WALER, sobre Reka UI e Tailwind CSS v4

2,928
@quidgest/ui

Quidgest's UI framework

22,574

Version History

Version Released Vue License
3.1.0 ^3.3.0
3.0.8 ^3.3.0
3.0.7 ^3.3.0
3.0.6 ^3.3.0
3.0.5 ^3.3.0