# How to Create Custom Visualizer Modes in Folia

> Learn to create custom visualizer modes in Folia. Add a folder, export VisualizerRegistryEntry, and implement a React component. Discover easy steps for custom Folia visualizations.

- Repository: [冬霧/folia-major](https://github.com/chthollyphile/folia-major)
- Tags: how-to-guide
- Published: 2026-07-06

---

**You create a custom visualizer mode in Folia by adding a folder under `src/components/visualizer/`, exporting a `VisualizerRegistryEntry` from an [`entry.tsx`](https://github.com/chthollyphile/folia-major/blob/main/entry.tsx) file using the `defineVisualizer` helper, and implementing a React component that conforms to `VisualizerSharedProps`.** The registry automatically discovers your mode via Vite's `import.meta.glob` with eager loading, requiring no manual imports or central configuration updates.

Folia’s visualizer system in the **chthollyphile/folia-major** repository uses a registry pattern that automatically discovers and loads every visualizer mode at build time. Adding a new mode requires only placing files in the correct directory structure and implementing the TypeScript contracts defined in [`src/components/visualizer/definition.ts`](https://github.com/chthollyphile/folia-major/blob/main/src/components/visualizer/definition.ts). This architecture lets you extend the application with custom audio visualizations while maintaining full integration with Folia’s theming, audio analysis, and settings infrastructure.

## Understanding the Visualizer Registry Architecture

The visualizer system centers on a registry runtime implemented in [`src/components/visualizer/registry.tsx`](https://github.com/chthollyphile/folia-major/blob/main/src/components/visualizer/registry.tsx). This module uses Vite’s `import.meta.glob<VisualizerEntryModule>('./*/entry.tsx', { eager: true })` to eagerly scan all [`entry.tsx`](https://github.com/chthollyphile/folia-major/blob/main/entry.tsx) files within subdirectories of `src/components/visualizer/`.

Because the discovery runs with `{ eager: true }`, modules load immediately at build time rather than lazily. The registry constructs two primary data structures: `VISUALIZER_REGISTRY`, an array of all entries, and `VISUALIZER_REGISTRY_BY_MODE`, a map keyed by each visualizer’s `mode` string. These structures expose utility functions including `getVisualizerRegistryEntry`, `hasVisualizerMode`, and `getVisualizerModeLabel` that the rest of the application uses to access visualizer metadata and render functions.

The type definitions in [`src/components/visualizer/definition.ts`](https://github.com/chthollyphile/folia-major/blob/main/src/components/visualizer/definition.ts) enforce the contract every mode must satisfy. The `VisualizerRegistryEntry` interface requires a unique identifier, ordering metadata, localization keys, preview configuration, and a render function. Optional fields include `renderSettingsPanel` for configuration UI and `resetSettings` for state management.

## Step-by-Step Implementation Guide

### Create the Module Folder and Entry File

Create a new subdirectory under `src/components/visualizer/` named after your mode. Within this folder, create an [`entry.tsx`](https://github.com/chthollyphile/folia-major/blob/main/entry.tsx) file. This file serves as the registration point that the registry automatically discovers.

The folder structure should follow the pattern:
- `src/components/visualizer/<mode>/entry.tsx`
- `src/components/visualizer/<mode>/Visualizer<Mode>.tsx`
- `src/components/visualizer/<mode>/settingsPanels.tsx` (optional)

### Define the Registry Entry with defineVisualizer

In [`entry.tsx`](https://github.com/chthollyphile/folia-major/blob/main/entry.tsx), import the `defineVisualizer` helper from `../definition` and call it with a configuration object satisfying `VisualizerRegistryEntry`. Required fields include:

- **mode**: A unique string identifier (e.g., `'sparkle'`)
- **order**: A number determining the visualizer’s position in UI selectors (higher values appear later)
- **labelKey**: Translation key for the visualizer name
- **labelFallback**: Default string if translation is missing
- **previewSeed**: Seed string for generating the mode’s thumbnail preview
- **tuningKind**: Audio analysis type (e.g., `'none'`, `'tilt'`, `'classic'`)
- **render**: A function component receiving `VisualizerSharedProps` and returning a React element

### Implement the Visualizer Component

Create a React component that accepts `VisualizerSharedProps`. This interface provides:

- `currentTime`: Audio playback position
- `lines`: Processed audio data for visualization
- `theme`: Current color theme object
- `seed`: Random seed for deterministic visuals
- Additional contextual properties

Implement your rendering logic using React, canvas, WebGL, or animation libraries. The component must return a React element that renders within Folia’s visualizer viewport.

### Add Optional Settings and Reset Logic

If your mode requires user configuration, export a `renderSettingsPanel` component typed to `VisualizerSettingsPanelProps`. This receives translation utilities and theme access. For modes with persisted custom settings, optionally export a `resetSettings` function that clears the tuning store.

## Complete Code Example: Building a "Sparkle" Visualizer

Below is a minimal implementation for a hypothetical "Sparkle" mode. Create the folder `src/components/visualizer/sparkle/` and add these three files:

```tsx
// src/components/visualizer/sparkle/VisualizerSparkle.tsx
import React from 'react';
import { VisualizerSharedProps } from '../definition';

export const VisualizerSparkle: React.FC<VisualizerSharedProps> = ({
  currentTime,
  lines,
  theme,
  seed,
}) => {
  // Example: render a flashing circle whose radius follows audio power
  const radius = Math.abs(Math.sin(currentTime.get())) * 50 + 20;

  return (
    <svg width="100%" height="100%" viewBox="0 0 200 200">
      <circle cx="100" cy="100" r={radius} fill={theme.primary} />
    </svg>
  );
};

```

```tsx
// src/components/visualizer/sparkle/SparkleSettingsPanel.tsx
import React from 'react';
import { VisualizerSettingsPanelProps } from '../definition';

export const SparkleSettingsPanel: React.FC<VisualizerSettingsPanelProps> = ({
  t,
  theme,
}) => (
  <div>
    <h3>{t('ui.visualizerSparkle')}</h3>
    <p>{t('ui.visualizerSparkleDescription')}</p>
  </div>
);

```

```tsx
// src/components/visualizer/sparkle/entry.tsx
import React from 'react';
import { defineVisualizer } from '../definition';
import { VisualizerSparkle } from './VisualizerSparkle';
import { SparkleSettingsPanel } from './SparkleSettingsPanel';

export default defineVisualizer({
  mode: 'sparkle',
  order: 50,
  labelKey: 'ui.visualizerSparkle',
  labelFallback: 'Sparkle',
  previewSeed: 'sparkle',
  previewStartOffset: 0,
  tuningKind: 'none',
  render: props => <VisualizerSparkle {...props} />,
  renderSettingsPanel: props => <SparkleSettingsPanel {...props} />,
});

```

## How the Registry Discovers New Modes

The automatic discovery mechanism relies on Vite’s module resolution. When the application builds, [`registry.tsx`](https://github.com/chthollyphile/folia-major/blob/main/registry.tsx) executes `import.meta.glob` to collect all [`entry.tsx`](https://github.com/chthollyphile/folia-major/blob/main/entry.tsx) files within visualizer subdirectories. The eager loading ensures all modules are available immediately at runtime.

The registry iterates through these modules, extracts the default export from each `defineVisualizer` call, and populates `VISUALIZER_REGISTRY` and `VISUALIZER_REGISTRY_BY_MODE`. When the user selects a visualizer from the UI, Folia calls `getVisualizerRegistryEntry(mode)` to retrieve the entry, then invokes the `render` function with `VisualizerSharedProps` containing real-time audio data and theme information.

This architecture means you never modify [`registry.tsx`](https://github.com/chthollyphile/folia-major/blob/main/registry.tsx) or any central configuration when adding modes. The system treats new visualizers as first-class plugins that receive full type safety and access to shared application state.

## Summary

- **Create a folder** under `src/components/visualizer/` with your mode name and add an [`entry.tsx`](https://github.com/chthollyphile/folia-major/blob/main/entry.tsx) file.
- **Export a registry entry** using `defineVisualizer` with required metadata including `mode`, `order`, and `render` function.
- **Implement `VisualizerSharedProps`** in your React component to receive audio data, theme, and timing information.
- **Optionally add** a `renderSettingsPanel` for configuration UI and `resetSettings` for state cleanup.
- **Let the registry handle discovery** automatically through Vite’s `import.meta.glob` with eager loading—no manual imports required.

## Frequently Asked Questions

### Do I need to manually import my visualizer in the main application entry point?

No. The registry in [`src/components/visualizer/registry.tsx`](https://github.com/chthollyphile/folia-major/blob/main/src/components/visualizer/registry.tsx) uses Vite’s `import.meta.glob<VisualizerEntryModule>('./*/entry.tsx', { eager: true })` to automatically discover and load every [`entry.tsx`](https://github.com/chthollyphile/folia-major/blob/main/entry.tsx) file within subdirectories of `src/components/visualizer/`. Your mode becomes available immediately upon saving the file, without any changes to central configuration or import statements.

### What TypeScript types must my visualizer component implement?

Your main visualizer component must accept props typed as `VisualizerSharedProps`, which includes `currentTime`, `lines` (audio analysis data), `theme` (color scheme), and `seed`. If you implement a settings panel, it must accept `VisualizerSettingsPanelProps`, which provides access to the translation function `t` and theme objects. Both types are exported from [`src/components/visualizer/definition.ts`](https://github.com/chthollyphile/folia-major/blob/main/src/components/visualizer/definition.ts).

### How do I control the order of visualizer modes in the UI selector?

The `order` field in your `defineVisualizer` configuration determines the visualizer’s position in UI selectors. The registry collects all entries and sorts them by this numeric value, with lower numbers appearing earlier in lists. Check existing modes in the repository to determine appropriate ordering values that place your visualizer where desired relative to built-in options.

### Can I implement custom state management or tuning controls for my visualizer?

Yes. Include a `renderSettingsPanel` function in your registry entry to render UI controls within Folia’s settings interface. For custom tuning state, use the `tuningKind` field to specify your analysis type, and optionally export a `resetSettings` function to clear persisted values. The settings panel receives `VisualizerSettingsPanelProps` and can implement sliders, color pickers, or any React components needed for configuration.