@twentyfourg/grimoire
v3.0.5
Published
A modern, headless Vue 3 component library built with TypeScript and Composition API. Create your own design system with unstyled, accessible components.
Downloads
718
Readme
Grimoire v3 
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
useStyledComponentcomposable - ♿ Accessible - Built with accessibility in mind
- 🎯 Direct Ref Access - No more
@refMountedevents 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/grimoireBasic 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 | | 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
@refMountedevents - ✅ 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:
- ❌
@refMountedevent 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
- Styled Component Migration Guide - Complete v2 → v3 migration guide
- Component Showcase - Interactive examples of all components
- Testing Strategy - How to test styled components
- Build Tools - Icon bundling with Iconify
- Refactoring Guide - Safe component refactoring workflow
🤝 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 testProject 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 scriptsDocumentation
dev-docs/- Internal documentation for Grimoire maintainersai_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
