How to Create Custom Visualizer Modes in Folia

You create a custom visualizer mode in Folia by adding a folder under src/components/visualizer/, exporting a VisualizerRegistryEntry from an 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. 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. This module uses Vite’s import.meta.glob<VisualizerEntryModule>('./*/entry.tsx', { eager: true }) to eagerly scan all 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 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 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, 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:

// 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>
  );
};
// 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>
);
// 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 executes import.meta.glob to collect all 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 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 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 uses Vite’s import.meta.glob<VisualizerEntryModule>('./*/entry.tsx', { eager: true }) to automatically discover and load every 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.

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.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →