https://github.com/marklearst/figma-vars-hooks
π³ Welcome to FigmaVars, a React hooks library designed to simplify the integration of Figma variables into your React applications.
https://github.com/marklearst/figma-vars-hooks
design-system design-systems design-tokens figma figma-api figma-design figma-to-code figma-variables react react-hooks react-library storybook storybook-design-system typescript typescript-library variables-css
Last synced: about 1 month ago
JSON representation
π³ Welcome to FigmaVars, a React hooks library designed to simplify the integration of Figma variables into your React applications.
- Host: GitHub
- URL: https://github.com/marklearst/figma-vars-hooks
- Owner: marklearst
- License: mit
- Created: 2025-06-12T18:03:22.000Z (11 months ago)
- Default Branch: main
- Last Pushed: 2025-08-27T17:53:37.000Z (8 months ago)
- Last Synced: 2025-09-18T10:11:23.640Z (7 months ago)
- Topics: design-system, design-systems, design-tokens, figma, figma-api, figma-design, figma-to-code, figma-variables, react, react-hooks, react-library, storybook, storybook-design-system, typescript, typescript-library, variables-css
- Language: TypeScript
- Homepage: https://www.npmjs.com/package/@figma-vars/hooks
- Size: 545 KB
- Stars: 0
- Watchers: 0
- Forks: 0
- Open Issues: 0
-
Metadata Files:
- Readme: README.md
- Changelog: CHANGELOG.md
- Contributing: CONTRIBUTING.md
- License: LICENSE
Awesome Lists containing this project
README
# FigmaVars/hooks
Built and maintained by Mark Learst.
A fast, typed React 19.2.3 hooks library for the Figma Variables API: fetch, update, and manage design tokens via the official [Figma REST API](https://www.figma.com/developers/api#variables).
Built for the modern web, this library provides a suite of hooks to fetch, manage, and mutate your design tokens/variables, making it easy to sync them between Figma and your React applications, Storybooks, or design system dashboards.
| Package | Quality | Activity |
| --------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------- |
| [](https://www.npmjs.com/package/@figma-vars/hooks) |  |  |
|  | [](https://codecov.io/gh/marklearst/figma-vars-hooks) |  |
|  |  |  |
|  |  |  |
## π Why 4.0.0
- β¨ **New Utilities**: `withRetry()` for automatic retry with exponential backoff, `redactToken()` for safe logging
- π§ **Flexible API**: `baseUrl` option for fetcher/mutator, `caseInsensitive` option for filterVariables
- π‘οΈ **Better Error Handling**: Improved parsing for non-JSON API responses (HTML, plain text)
- π **Critical Bug Fix**: SWR cache keys now correctly separate fallback and live data
- π **Improved Docs**: Comprehensive mutation return type documentation with examples
- π¦ **Modern Tooling**: Node 20+ toolchain, strict TypeScript, and ESM-first packaging with CJS interop
- π₯οΈ **CLI Export Tool**: Automate variable exports with `figma-vars-export` for CI/CD (Enterprise required)
> β οΈ **Breaking Change**: `useFigmaToken` is now a named export. See [Migration Guide](#-migration-guide-3x--40).
> β
**4.1.1 patch**: Fixed root utility exports and fallback key handling when fallback JSON is invalid.
## π Features at a Glance
- **Modern React 19.2 hooks** for variables, collections, modes, and published variables
- **Ergonomic mutation hooks** with consistent loading/error states
- **SWR configuration support** for customizing caching and revalidation behavior
- **Error handling utilities** for type-safe error checking and status code access
- **Cache invalidation helpers** for automatic data refresh after mutations
- **CLI export tool** (`figma-vars-export`) for automating variable exports to JSON (Enterprise required)
- **Fallback JSON support** (object or string) for offline/static use - works without Enterprise!
- **Typed core entrypoint** for non-React consumers (Axios, TanStack Query, server scripts)
- **100% test coverage** + strict TypeScript + clean exports/attw/publint/size-limit checks
## π¦ Install
```bash
npm install @figma-vars/hooks
# or
pnpm add @figma-vars/hooks
```
Peer deps: `react` and `react-dom`.
## π₯οΈ CLI Export Tool
The package includes a **CLI tool** (`figma-vars-export`) for automatically exporting Figma variables to JSON via the REST API. Perfect for CI/CD pipelines, build scripts, or one-off exports.
> β οΈ **Enterprise Required**: The CLI tool uses the Figma Variables REST API, which requires a **Figma Enterprise account**. Without Enterprise, use the [Dev Mode plugin export](#exporting-variables-for-fallback) method instead.
```bash
# Using npx (no install needed)
FIGMA_TOKEN=your_token npx figma-vars-export --file-key YOUR_FILE_KEY --out ./variables.json
# After installing
npm install @figma-vars/hooks
FIGMA_TOKEN=your_token figma-vars-export --file-key YOUR_FILE_KEY --out ./variables.json
# Show help
figma-vars-export --help
```
**Options:**
- `--file-key` - Figma file key (required, or set `FIGMA_FILE_KEY` env var)
- `--out` - Output path (default: `data/figma-variables.json`)
- `--help` - Show help message
**Environment Variables:**
- `FIGMA_TOKEN` or `FIGMA_PAT` - Figma Personal Access Token (required)
- `FIGMA_FILE_KEY` - Figma file key (optional)
**Example Output:**
```
Saved variables to ./variables.json
Variables count: 42
```
**No Enterprise?** See [Exporting variables for fallback](#exporting-variables-for-fallback) for alternative methods that work without Enterprise.
## π οΈ Quick Start (SWR-powered hooks)
```tsx
import { FigmaVarsProvider, useVariables } from '@figma-vars/hooks'
const FIGMA_TOKEN = import.meta.env.VITE_FIGMA_TOKEN
const FIGMA_FILE_KEY = 'your-file-key'
function App() {
return (
)
}
function Tokens() {
const { data, isLoading, error } = useVariables()
if (isLoading) return
Loadingβ¦
if (error) return Error: {error.message}
const variables = Object.values(data?.meta.variables ?? {})
return {JSON.stringify(variables, null, 2)}
}
```
## π§© Non-SWR Usage (Core entrypoint)
Use the `/core` build when you prefer Axios/TanStack/server scripts without React/SWR.
**Axios example (GET + bulk PUT)**
```ts
import axios from 'axios'
import { FIGMA_FILE_VARIABLES_PATH } from '@figma-vars/hooks/core'
const token = process.env.FIGMA_TOKEN!
const fileKey = process.env.FIGMA_FILE_KEY!
// Fetch local variables
const url = `https://api.figma.com${FIGMA_FILE_VARIABLES_PATH(fileKey)}/local`
const { data } = await axios.get(url, {
headers: { 'X-FIGMA-TOKEN': token, 'Content-Type': 'application/json' },
})
// Bulk update
await axios.put(
`https://api.figma.com${FIGMA_FILE_VARIABLES_PATH(fileKey)}`,
{ variables: [{ action: 'UPDATE', id: 'VariableId:123', name: 'new-name' }] },
{ headers: { 'X-FIGMA-TOKEN': token, 'Content-Type': 'application/json' } }
)
```
**TanStack Query example**
```ts
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query'
import { FIGMA_FILE_VARIABLES_PATH, fetcher, mutator } from '@figma-vars/hooks/core'
const token = process.env.FIGMA_TOKEN!
const fileKey = process.env.FIGMA_FILE_KEY!
export function useLocalVariables() {
return useQuery({
queryKey: ['figma-local', fileKey],
queryFn: () => fetcher(`${FIGMA_FILE_VARIABLES_PATH(fileKey)}/local`, token),
staleTime: 60_000,
})
}
export function useBulkUpdate() {
const qc = useQueryClient()
return useMutation({
mutationFn: (payload: unknown) =>
mutator(FIGMA_FILE_VARIABLES_PATH(fileKey), token, 'UPDATE', payload),
onSuccess: () => qc.invalidateQueries({ queryKey: ['figma-local', fileKey] }),
})
}
```
## π‘οΈ Fallback JSON (offline/static)
Pass `fallbackFile` (object or JSON string) to `FigmaVarsProvider` to bypass live API calls:
```tsx
import exportedVariables from './figma-variables.json'
;
```
### Exporting variables for fallback
There are several ways to get your Figma variables as JSON:
1. **Dev Mode / plugin export (recommended, no Enterprise needed)** β
- Use a Variables exporter plugin in Figma Dev Mode to download the full Variables panel as JSON
- Save anywhere (e.g., `data/figma-variables.json`) and pass it to `fallbackFile`
- Works for everyone, no Enterprise account required!
2. **CLI export tool (Enterprise required)** π
- Automatically exports via REST API - perfect for CI/CD and automation
- See the [CLI Export Tool](#-cli-export-tool) section above for full usage details
- Also available from cloned repo: `node scripts/export-variables.mjs --file-key KEY --out file.json`
- **Desktop MCP (manual/partial)**: Selecting a frame and running `get_variable_defs` returns only that selectionβs variables. Use plugin/REST exports for complete coverage.
4. **Style Dictionary**
- Once you have the JSON (from any path), feed it into Style Dictionary to emit platform-specific artifacts
- Or import it directly via `fallbackFile`
## π§ Mutation Hooks (verbs fixed)
- `useCreateVariable` β POST via bulk endpoint with `action: 'CREATE'`
- `useUpdateVariable` β PUT via bulk endpoint with `action: 'UPDATE'`
- `useDeleteVariable` β DELETE via bulk endpoint with `action: 'DELETE'`
- `useBulkUpdateVariables` β PUT bulk payload (collections, modes, variables, values)
All return `{ mutate, data, error, isLoading, isSuccess, isError }`.
### Example: Creating and updating variables
```tsx
import { useCreateVariable, useUpdateVariable, useInvalidateVariables } from '@figma-vars/hooks'
function VariableEditor() {
const { mutate: create } = useCreateVariable()
const { mutate: update } = useUpdateVariable()
const { invalidate } = useInvalidateVariables()
const handleCreate = async () => {
await create({
name: 'Primary Color',
variableCollectionId: 'CollectionId:123',
resolvedType: 'COLOR',
})
invalidate() // Refresh cache after mutation
}
const handleUpdate = async (id: string) => {
await update({
variableId: id,
payload: { name: 'Updated Name' },
})
invalidate() // Refresh cache after mutation
}
return (
<>
Create Variable
handleUpdate('VariableId:123')}>Update
>
)
}
```
## π‘οΈ Error Handling
### Error Boundaries (Recommended)
Wrap your Figma-connected components with an error boundary to gracefully handle errors:
```tsx
import { ErrorBoundary } from 'react-error-boundary'
import { FigmaVarsProvider } from '@figma-vars/hooks'
function FigmaErrorFallback({ error }: { error: Error }) {
return (
Failed to load Figma data
{error.message}
)
}
function App() {
return (
)
}
```
> **Note:** The provider validates fallback file structure at runtime and logs warnings in development. Invalid fallback data won't crash the app but will result in `undefined` data.
### Runtime Validation
Use type guards to validate data at runtime:
```tsx
import { isLocalVariablesResponse, isPublishedVariablesResponse } from '@figma-vars/hooks'
// Validate before using
if (isLocalVariablesResponse(data)) {
// Safe to access data.meta.variables
}
```
### Error Utilities
The library includes powerful error handling utilities for type-safe error checking:
```tsx
import { isFigmaApiError, getErrorStatus, getErrorMessage, hasErrorStatus } from '@figma-vars/hooks'
function ErrorHandler({ error }: { error: Error | null }) {
if (!error) return null
// Type guard for FigmaApiError
if (isFigmaApiError(error)) {
const status = error.statusCode
if (status === 401) {
return
Authentication required. Please check your token.
}
if (status === 403) {
return Access forbidden. Check file permissions.
}
if (status === 429) {
return Rate limit exceeded. Please wait before retrying.
}
if (status === 404) {
return File or variable not found.
}
}
// Helper functions
const status = getErrorStatus(error) // number | null
const message = getErrorMessage(error) // string
// Convenience check
if (hasErrorStatus(error, 401)) {
// Handle unauthorized
}
return
Error: {message}
}
```
**Common HTTP Status Codes:**
- `401` - Unauthorized (invalid or missing token)
- `403` - Forbidden (insufficient permissions)
- `404` - Not Found (file/variable doesn't exist)
- `429` - Too Many Requests (rate limit exceeded)
## π Cache Management
After mutations, use `useInvalidateVariables` to refresh cached data:
```tsx
import { useUpdateVariable, useInvalidateVariables } from '@figma-vars/hooks'
function UpdateButton({ variableId }: { variableId: string }) {
const { mutate, isLoading } = useUpdateVariable()
const { invalidate, revalidate } = useInvalidateVariables()
const handleUpdate = async () => {
await mutate({
variableId,
payload: { name: 'New Name' },
})
// Option 1: Invalidate (refetch on next access)
invalidate()
// Option 2: Revalidate immediately (refetch now)
// revalidate()
}
return (
{isLoading ? 'Updating...' : 'Update Variable'}
)
}
```
## βοΈ SWR Configuration
Customize SWR behavior globally through the provider:
```tsx
{
// Global error handler
if (isFigmaApiError(error) && error.statusCode === 429) {
console.warn('Rate limited, backing off...')
}
},
}}>
```
**Common SWR Options:**
- `revalidateOnFocus` - Refetch when window regains focus (default: `true`)
- `dedupingInterval` - Deduplication interval in ms (default: `2000`)
- `errorRetryCount` - Max retry attempts (default: `5`)
- `refreshInterval` - Polling interval in ms (default: `0` = disabled)
- `onError` - Global error callback
## π API Cheat Sheet
### Hooks
- **Queries**: `useVariables` (local), `usePublishedVariables` (library/published), `useVariableCollections`, `useVariableModes`, `useFigmaToken`
- **Granular Selectors**: `useCollectionById`, `useModesByCollection`, `useVariableById` (optimized selectors for specific entities)
- **Mutations**: `useCreateVariable`, `useUpdateVariable`, `useDeleteVariable`, `useBulkUpdateVariables`
- **Cache**: `useInvalidateVariables` (invalidate/revalidate cache)
### Utilities
- **Filtering**: `filterVariables` (filter by type, name, with optional `caseInsensitive` matching)
- **Retry**: `withRetry` (automatic retry with exponential backoff for rate limits)
- **Security**: `redactToken` (safely redact tokens for logging/display)
- **Error Handling**: `isFigmaApiError`, `getErrorStatus`, `getErrorMessage`, `hasErrorStatus`, `isRateLimited`, `getRetryAfter`
- **Type Guards**: `isLocalVariablesResponse`, `isPublishedVariablesResponse`, `validateFallbackData` (runtime validation)
- **SWR Keys**: `getVariablesKey`, `getPublishedVariablesKey`, `getInvalidationKeys` (centralized cache key construction)
- **Core helpers**: `fetcher`, `mutator` (with `baseUrl` option), constants for endpoints and headers
### Types
- **Responses**: `LocalVariablesResponse`, `PublishedVariablesResponse`
- **Variables**: `FigmaVariable`, `FigmaCollection`, `VariableMode`
- **Mutations**: `BulkUpdatePayload`, `CreateVariablePayload`, `UpdateVariablePayload`
- **Errors**: `FigmaApiError` (extends `Error` with `statusCode`)
## π Auth & Scope
- Header: `X-FIGMA-TOKEN: `
- Scopes: `file_variables:read` for GETs, `file_variables:write` for mutations.
- Enterprise Full seat required for live API; fallback JSON works without a token.
## β οΈ Enterprise Requirement and Offline Options
- The Figma Variables REST API requires a Figma Enterprise seat for live requests. Without Enterprise, live calls will fail even with a valid PAT.
- The library remains useful without Enterprise: supply `fallbackFile` (object or JSON string) exported from Figma (Dev Mode plugin, CLI, or Figma MCP server output) and all read hooks work offline/for static deployments.
- MCP/other exporters: as long as they emit the same JSON shape as the Variables API, you can feed that JSON into `fallbackFile`; mutations still require Enterprise access.
## π« Do Not Publish Tokens or File Keys
- Never commit PATs or file keys to git, Storybook static builds, or client bundles.
- Use environment variables (`process.env` / `import.meta.env`) and secret managers; keep them server-side where possible.
- Prefer `fallbackFile` with `token={null}`/`fileKey={null}` for demos and public Storybooks.
- Use `redactToken()` when logging tokens for debugging:
```ts
import { redactToken } from '@figma-vars/hooks'
// Safe logging
console.log('Using token:', redactToken(token))
// Output: "Using token: figd_***...***cret"
```
## π Rate Limits
- Figma enforces per-token limits. Rely on SWR/TanStack caching, avoid unnecessary refetches, and prefer fallback JSON for static sites.
- Use `swrConfig` to customize `dedupingInterval` and `errorRetryCount` to optimize API usage.
- Use `withRetry()` utility for automatic retry with exponential backoff on 429 errors:
```ts
import { withRetry, fetcher } from '@figma-vars/hooks'
const fetchWithRetry = withRetry(() => fetcher('/v1/files/KEY/variables/local', token), {
maxRetries: 3,
onRetry: (attempt, delay) => console.log(`Retry ${attempt}...`),
})
```
## π Storybook & Next.js
- **Storybook decorator**: wrap stories once so hooks have context and tokens.
```tsx
// .storybook/preview.tsx
import { FigmaVarsProvider } from '@figma-vars/hooks'
import type { Preview } from '@storybook/react'
const FIGMA_TOKEN = process.env.STORYBOOK_FIGMA_TOKEN
const FIGMA_FILE_KEY = process.env.STORYBOOK_FIGMA_FILE_KEY
const preview: Preview = {
decorators: [
Story => (
),
],
}
export default preview
```
- **Next.js App Router**: provide context in a shared provider file.
```tsx
// app/providers.tsx
import { FigmaVarsProvider } from '@figma-vars/hooks'
export function Providers({ children }: { children: React.ReactNode }) {
return (
{children}
)
}
```
## π§ͺ Tooling & Quality Gates
- `pnpm run build`, `pnpm test`, `pnpm run test:coverage`
- `pnpm run check:publint`, `pnpm run check:attw`, `pnpm run check:size`
## π§ Release Checklist (4.x)
- Run `pnpm run check:release`
- Bump the version with `pnpm version patch|minor|major` (creates matching git tag, e.g. `v4.1.1`)
- Push commit + tags (`git push && git push --tags`) so release workflows can run
- CI publishes to npm automatically
- Update dist-tags on npm if needed (`latest` β current stable)
**Backfill tags if missing:**
```bash
git tag -a v4.1.0 -m "v4.1.0"
git tag -a v4.1.1 -m "v4.1.1"
git push --tags
```
## π Migration Guide (3.x β 4.0)
### Breaking Change: `useFigmaToken` Export
```tsx
// Before (3.x) - NO LONGER WORKS
import useFigmaToken from '@figma-vars/hooks'
// After (4.0) - USE THIS
import { useFigmaToken } from '@figma-vars/hooks'
```
### New Utilities (opt-in)
```ts
import { withRetry, redactToken, filterVariables } from '@figma-vars/hooks'
// Automatic retry with exponential backoff
const fetchWithRetry = withRetry(() => myApiCall(), { maxRetries: 3 })
// Safe token logging
console.log('Token:', redactToken(token)) // "figd_***...***cret"
// Case-insensitive filtering
filterVariables(vars, { name: 'primary', caseInsensitive: true })
```
### Custom API Base URL
```ts
import { fetcher, mutator } from '@figma-vars/hooks/core'
// Use mock server for testing
await fetcher('/v1/files/KEY/variables/local', token, {
baseUrl: 'http://localhost:3000',
})
```
---
## π Contributing
PRs and issues are welcome! Please see [CONTRIBUTING.md](CONTRIBUTING.md) for guidelines.
## π License
This project is licensed under the [MIT License](LICENSE).
Β© 2024β2026 Mark Learst