@renge-ui/vue
Composables for theme management.
Vue 3 composables for Renge theme management. Reactive state, provide/inject pattern, full token integration via CSS custom properties.
Overview
What is @renge-ui/vue?
A Vue 3-first adapter for Renge tokens.
@renge-ui/vue provides Vue 3 composables and provide/inject utilities for Renge theme management. It uses reactive refs and the Vue 3 Composition API to provide a clean, modern interface for consuming Renge tokens.
Key features: useRengeTheme() composable with reactive state, useRengeInject() for child component access, provide/inject pattern for component tree sharing, export of all Renge tokens and constants (PHI, FIBONACCI, GOLDEN_ANGLE).
Getting Started
Setup in three steps.
Install, import tokens, and use the composable.
1. Install Packages
Add both @renge-ui/vue and @renge-ui/tokens to your project.
pnpm add @renge-ui/vue @renge-ui/tokens2. Import Tokens
Add CSS tokens to your main.js or global stylesheet.
// main.js
import '@renge-ui/tokens/renge.css';
import { createApp } from 'vue';
import App from './App.vue';
createApp(App).mount('#app');3. Use Composable
Call useRengeTheme() in your root component to provide theme state to the component tree.
<template>
<div :style="{ background: 'var(--renge-color-bg)', color: 'var(--renge-color-fg)' }">
<p>Current Profile: {{ profile }}</p>
<p>Current Mode: {{ mode }}</p>
<button @click="switchProfile('earth')">Switch to Earth</button>
<button @click="switchMode(mode === 'dark' ? 'light' : 'dark')">Toggle Mode</button>
</div>
</template>
<script setup>
import { useRengeTheme } from '@renge-ui/vue';
const { profile, mode, switchProfile, switchMode } = useRengeTheme();
</script>API Reference
useRengeTheme() composable
Complete composable reference with types and examples.
useRengeTheme()
Root composable that provides theme state and methods. Call this in your root component or layout.
<script setup>
import { useRengeTheme } from '@renge-ui/vue';
const {
profile, // ref<string> current profile
mode, // ref<'light' | 'dark'> current mode
switchProfile, // (name: string) => void
switchMode, // (m: 'light' | 'dark') => void
} = useRengeTheme();
</script>useRengeInject()
Child component composable that accesses the provided theme context. Must be called inside a component tree that has called useRengeTheme().
<script setup>
import { useRengeInject } from '@renge-ui/vue';
const { profile, mode, switchProfile, switchMode } = useRengeInject();
// Will throw if useRengeTheme() was not called in parent
</script>Type Definitions
export interface RengeThemeState {
profile: string;
mode: 'light' | 'dark';
}
export interface RengeThemeContext extends RengeThemeState {
switchProfile: (name: string) => void;
switchMode: (mode: 'light' | 'dark') => void;
}🔜 Coming Soon: switchScale
Runtime base unit scaling with reactive updates across all spacing, radius, and dimension tokens.
Patterns
Common use cases
Real-world examples of theme switching in Vue applications.
localStorage contract: switchProfile and switchMode update the reactive ref and apply CSS to the DOM immediately. They do not automatically persist to localStorage or read from it on mount. You are responsible for calling them with stored values in onMounted and writing back in a watch. The examples below show the full pattern.
Persist Theme to localStorage
Save and restore theme from localStorage.
<script setup>
import { useRengeTheme } from '@renge-ui/vue';
import { onMounted, watch } from 'vue';
const { profile, mode, switchProfile, switchMode } = useRengeTheme();
// Load from localStorage on mount
onMounted(() => {
const savedProfile = localStorage.getItem('renge-profile');
const savedMode = localStorage.getItem('renge-mode');
if (savedProfile) switchProfile(savedProfile);
if (savedMode) switchMode(savedMode as 'light' | 'dark');
});
// Save changes to localStorage
watch(profile, (newProfile) => {
localStorage.setItem('renge-profile', newProfile);
});
watch(mode, (newMode) => {
localStorage.setItem('renge-mode', newMode);
});
</script>Respect System Preference
Detect system dark mode and switch automatically on mount.
<script setup>
import { useRengeTheme } from '@renge-ui/vue';
import { onMounted } from 'vue';
const { switchMode } = useRengeTheme();
onMounted(() => {
if (window.matchMedia('(prefers-color-scheme: dark)').matches) {
switchMode('dark');
}
});
</script>Theme Switcher Component
Complete profile switcher with all available profiles.
<template>
<div style="display: flex; gap: 8px; flex-wrap: wrap;">
<button
v-for="p in profiles"
:key="p"
:class="{ active: profile === p }"
@click="switchProfile(p)"
>
{{ p.charAt(0).toUpperCase() + p.slice(1) }}
</button>
</div>
</template>
<script setup>
import { useRengeInject } from '@renge-ui/vue';
const { profile, switchProfile } = useRengeInject();
const profiles = ['ocean', 'earth', 'twilight', 'fire', 'void', 'leaf'];
</script>
<style scoped>
button {
padding: var(--renge-space-2) var(--renge-space-3);
border: 1px solid var(--renge-color-border);
border-radius: var(--renge-radius-2);
background: var(--renge-color-bg-subtle);
color: var(--renge-color-fg);
cursor: pointer;
transition: all var(--renge-duration-2) var(--renge-easing-ease-out);
}
button:hover {
border-color: var(--renge-color-accent);
}
button.active {
background: var(--renge-color-accent);
color: var(--renge-color-bg);
border-color: var(--renge-color-accent);
}
</style>Watched Reactive Changes
React to theme changes with watchers for side effects.
<script setup>
import { useRengeTheme } from '@renge-ui/vue';
import { watch } from 'vue';
const { profile, mode } = useRengeTheme();
// Watch profile changes
watch(profile, (newProfile) => {
console.log('Profile changed to:', newProfile);
// Send analytics, update app state, etc.
});
// Watch mode changes
watch(mode, (newMode) => {
console.log('Mode changed to:', newMode);
// Update UI, theme provider, etc.
});
// Watch multiple
watch([profile, mode], ([newProfile, newMode]) => {
console.log('Theme updated:', { profile: newProfile, mode: newMode });
});
</script>Global Provide/Inject Setup
Provide theme globally from root component or App.vue.
<!-- App.vue -->
<template>
<div>
<ThemedContent />
</div>
</template>
<script setup>
import { useRengeTheme } from '@renge-ui/vue';
import ThemedContent from './components/ThemedContent.vue';
const { profile, mode, switchProfile, switchMode } = useRengeTheme();
</script>
<!-- Any child component -->
<template>
<button @click="switchProfile('twilight')">
Current: {{ profile }}
</button>
</template>
<script setup>
import { useRengeInject } from '@renge-ui/vue';
const { profile, switchProfile } = useRengeInject();
</script>Integration
Framework patterns
Integrate Renge with Vue ecosystem tools and frameworks.
Nuxt 3 Setup
Use middleware or composable for SSR-safe theme initialization.
// composables/useTheme.ts
import { useRengeTheme } from '@renge-ui/vue';
export function useAppTheme() {
const theme = useRengeTheme();
if (process.client) {
// Load from localStorage on client only
const saved = {
profile: localStorage.getItem('renge-profile'),
mode: localStorage.getItem('renge-mode'),
};
if (saved.profile) theme.switchProfile(saved.profile);
if (saved.mode) theme.switchMode(saved.mode as 'light' | 'dark');
}
return theme;
}
// app.vue
<script setup>
import { useAppTheme } from '~/composables/useTheme';
useAppTheme();
</script>Vite + Vue 3 Setup
Standard Vite + Vue 3 project configuration.
// main.ts
import '@renge-ui/tokens/renge.css';
import { createApp } from 'vue';
import App from './App.vue';
createApp(App).mount('#app');
// App.vue
<template>
<div id="app">
<RouterView />
</div>
</template>
<script setup>
import { useRengeTheme } from '@renge-ui/vue';
useRengeTheme();
</script>Shared Composable Module
Create a reusable theme composable for consistency.
// composables/theme.ts
import { useRengeTheme } from '@renge-ui/vue';
import { onMounted } from 'vue';
export function useTheme() {
const theme = useRengeTheme();
onMounted(() => {
// Restore from localStorage
const saved = {
profile: localStorage.getItem('renge-profile'),
mode: localStorage.getItem('renge-mode'),
};
if (saved.profile) theme.switchProfile(saved.profile);
if (saved.mode) theme.switchMode(saved.mode as 'light' | 'dark');
});
return theme;
}
// Usage in any component
<script setup>
import { useTheme } from '@/composables/theme';
const { profile, switchProfile } = useTheme();
</script>Server-Side Rendering (SSR)
Handle theme initialization safely in SSR environments.
// app.vue
<template>
<div :data-profile="profile" :data-mode="mode">
<RouterView />
</div>
</template>
<script setup>
import { useRengeTheme } from '@renge-ui/vue';
import { onBeforeMount, onMounted } from 'vue';
const { profile, mode, switchProfile, switchMode } = useRengeTheme();
// SSR: set from cookie or header
if (import.meta.env.SSR) {
// Get theme from server context
// switchProfile(serverTheme.profile);
// switchMode(serverTheme.mode);
}
// Client: restore from localStorage
onMounted(() => {
const saved = {
profile: localStorage.getItem('renge-profile'),
mode: localStorage.getItem('renge-mode'),
};
if (saved.profile) switchProfile(saved.profile);
if (saved.mode) switchMode(saved.mode as 'light' | 'dark');
});
</script>Best Practices
Vue-specific patterns
Recommended approaches for using Renge in Vue applications.
Use computed for derived state
<script setup>
import { useRengeInject } from '@renge-ui/vue';
import { computed } from 'vue';
const { profile } = useRengeInject();
// Good: computed derived state
const isDarkProfile = computed(() =>
['twilight', 'void'].includes(profile.value)
);
// Avoid: manual reactive updates
// let isDarkProfile = ref(false);
// watch(profile, p => { isDarkProfile.value = ['twilight', 'void'].includes(p); });
</script>Prefer CSS variables in styles
<template>
<!-- Good: CSS variables always in sync -->
<div :style="{ background: 'var(--renge-color-bg)' }">
Content
</div>
<!-- Acceptable: use rengeVars for computed values -->
<div :style="{ padding: rengeVars.space['4'] }">
Content
</div>
</template>
<script setup>
import { rengeVars } from '@renge-ui/vue';
</script>Watch changes with debouncing
<script setup>
import { useRengeTheme } from '@renge-ui/vue';
import { watch } from 'vue';
import { useDebounceFn } from '@vueuse/core';
const { profile } = useRengeTheme();
const saveTheme = useDebounceFn((newProfile) => {
localStorage.setItem('renge-profile', newProfile);
}, 500);
watch(profile, saveTheme);
</script>Error handling for useRengeInject
<script setup>
import { useRengeInject } from '@renge-ui/vue';
let theme;
try {
theme = useRengeInject();
} catch (error) {
// Handle case where provider is not available
console.warn('useRengeTheme() was not called in parent');
// Use fallback or default values
}
</script>Tokens
Exported tokens
Direct access to Renge tokens for programmatic use.
rengeVars
Complete token object with type definitions.
<script setup>
import { rengeVars } from '@renge-ui/vue';
// Access any token
const spacing = rengeVars.space['4'];
const color = rengeVars.color.fg;
const radius = rengeVars.radius['2'];
</script>Mathematical Constants
<script setup>
import { PHI, FIBONACCI, GOLDEN_ANGLE } from '@renge-ui/vue';
// PHI = 1.618033988749895
// FIBONACCI = [1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, ...]
// GOLDEN_ANGLE = 137.50776...
</script>