Skip to main content
Tailor speak-mintlify to match your documentation’s unique needs and branding.

Audio Component Customization

The audio player component is fully customizable. Start with the provided template and modify it to match your design system.

Component Configuration

Specify your custom component in speaker-config.yaml:
speaker-config.yaml
component:
  import: /snippets/audio-transcript.jsx  # Path to your component
  name: AudioTranscript                    # Component name
The import path is relative to your documentation root.

Component Props Interface

Your component receives this props structure:
interface AudioTranscriptProps {
  voices: Array<{
    name: string;    // Display name (from speaker-config.yaml)
    url: string;     // S3 public URL to audio file
  }>;
}

Example: Minimal Player

Create a simple, lightweight audio player:
/snippets/minimal-audio.jsx
export const MinimalAudio = ({ voices = [] }) => {
  const audioUrl = voices[0]?.url;

  if (!audioUrl) return null;

  return (
    <div className="my-4 p-4 border rounded-lg">
      <div className="flex items-center gap-3">
        <svg className="w-5 h-5" fill="currentColor" viewBox="0 0 20 20">
          <path d="M10 12a2 2 0 100-4 2 2 0 000 4z" />
          <path fillRule="evenodd" d="M.458 10C1.732 5.943 5.522 3 10 3s8.268 2.943 9.542 7c-1.274 4.057-5.064 7-9.542 7S1.732 14.057.458 10zM14 10a4 4 0 11-8 0 4 4 0 018 0z" clipRule="evenodd" />
        </svg>
        <audio controls className="flex-1" src={audioUrl}>
          Your browser does not support audio playback.
        </audio>
      </div>
    </div>
  );
};
Update config:
speaker-config.yaml
component:
  import: /snippets/minimal-audio.jsx
  name: MinimalAudio

Example: Multi-Voice Tabs

Display voices as tabs instead of a dropdown:
/snippets/tabbed-audio.jsx
import { useState } from 'react';

export const TabbedAudio = ({ voices = [] }) => {
  const [activeTab, setActiveTab] = useState(0);

  if (voices.length === 0) return null;

  return (
    <div className="border rounded-lg overflow-hidden">
      {/* Tabs */}
      {voices.length > 1 && (
        <div className="flex bg-gray-100 dark:bg-gray-800">
          {voices.map((voice, idx) => (
            <button
              key={idx}
              onClick={() => setActiveTab(idx)}
              className={`px-4 py-2 text-sm font-medium transition-colors ${
                idx === activeTab
                  ? 'bg-white dark:bg-gray-900 text-blue-600'
                  : 'text-gray-600 hover:text-gray-900'
              }`}
            >
              {voice.name}
            </button>
          ))}
        </div>
      )}

      {/* Audio Player */}
      <div className="p-4 bg-white dark:bg-gray-900">
        <audio
          key={voices[activeTab].url}
          controls
          className="w-full"
          src={voices[activeTab].url}
        >
          Your browser does not support audio.
        </audio>
      </div>
    </div>
  );
};

Example: Branded Player

Match your brand colors and style:
/snippets/branded-audio.jsx
export const BrandedAudio = ({ voices = [] }) => {
  return (
    <div className="my-6 rounded-xl overflow-hidden shadow-lg bg-gradient-to-r from-purple-500 to-pink-500 p-0.5">
      <div className="bg-white dark:bg-gray-900 rounded-xl p-6">
        <div className="flex items-center gap-3 mb-4">
          <div className="w-10 h-10 bg-gradient-to-r from-purple-500 to-pink-500 rounded-full flex items-center justify-center">
            <svg className="w-6 h-6 text-white" fill="currentColor" viewBox="0 0 20 20">
              <path d="M18 3a1 1 0 00-1.196-.98l-10 2A1 1 0 006 5v9.114A4.369 4.369 0 005 14c-1.657 0-3 .895-3 2s1.343 2 3 2 3-.895 3-2V7.82l8-1.6v5.894A4.37 4.37 0 0015 12c-1.657 0-3 .895-3 2s1.343 2 3 2 3-.895 3-2V3z" />
            </svg>
          </div>
          <div>
            <h4 className="font-bold text-lg">Listen to this page</h4>
            <p className="text-sm text-gray-600 dark:text-gray-400">Choose your preferred voice</p>
          </div>
        </div>

        {voices.map((voice, idx) => (
          <div key={idx} className="mb-3 last:mb-0">
            <label className="block text-sm font-medium mb-1">{voice.name}</label>
            <audio controls className="w-full" src={voice.url} />
          </div>
        ))}
      </div>
    </div>
  );
};

File Pattern Customization

Custom File Patterns

Control which files get processed:
npx speak-mintlify generate .

Using .speakignore

Create a .speakignore file at your repository root:
.speakignore
# Exclude API reference pages
api-reference/**

# Exclude reusable snippets
snippets/**

# Exclude legal pages
legal/**
terms.mdx
privacy.mdx

# Exclude drafts and work-in-progress
drafts/**
wip/**
*.draft.mdx

# Exclude changelog
CHANGELOG.md
release-notes/**

# Exclude specific files
index.mdx
404.mdx
Use .speakignore to reduce API costs by excluding pages that don’t benefit from audio narration.

Voice Configuration Strategies

Strategy 1: Single Professional Voice

Best for: Corporate docs, consistent brand voice
speaker-config.yaml
voices:
  8ef4a238714b45718ce04243307c57a7: Professional Narrator

Strategy 2: Multiple Personality Voices

Best for: Developer tools, friendly tone
speaker-config.yaml
voices:
  8ef4a238714b45718ce04243307c57a7: Sarah (Friendly)
  bf322df2096a46f18c579d0baa36f41d: Adrian (Technical)
  933563129e564b19a115bedd57b7406a: Alex (Casual)

Strategy 3: Multilingual Support

Best for: International audience
speaker-config.yaml
voices:
  8ef4a238714b45718ce04243307c57a7: English
  a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6: Español
  b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6q7: Français
  c3d4e5f6g7h8i9j0k1l2m3n4o5p6q7r8: 日本語

Strategy 4: Role-Based Voices

Best for: Tutorial content, conversational docs
speaker-config.yaml
voices:
  8ef4a238714b45718ce04243307c57a7: Instructor
  bf322df2096a46f18c579d0baa36f41d: Student Guide
  933563129e564b19a115bedd57b7406a: Expert Tips

S3 Path Customization

Customize how audio files are organized in S3:
# Default: audio/
npx speak-mintlify generate .
# Result: s3://bucket/audio/page-slug/voice-id.mp3

# Custom prefix
npx speak-mintlify generate . --s3-path-prefix "tts"
# Result: s3://bucket/tts/page-slug/voice-id.mp3

# Versioned audio
npx speak-mintlify generate . --s3-path-prefix "audio/v2"
# Result: s3://bucket/audio/v2/page-slug/voice-id.mp3
Update in config:
speaker-config.yaml
voices:
  8ef4a238714b45718ce04243307c57a7: Main Voice

# This is a comment - CLI flag would be:
# --s3-path-prefix "custom-path"
S3 path prefix can only be set via CLI flag, not in speaker-config.yaml.

Advanced Workflows

Conditional Generation

Generate audio only for specific branches:
.github/workflows/tts-conditional.yaml
name: Conditional TTS

on:
  push:
    branches:
      - main
      - production

jobs:
  generate:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: '20'

      # Generate for production with all voices
      - name: Generate (production)
        if: github.ref == 'refs/heads/production'
        run: npx speak-mintlify generate .
        env:
          FISH_API_KEY: ${{ secrets.FISH_API_KEY }}
          # ... other env vars

      # Generate for main with single voice
      - name: Generate (staging)
        if: github.ref == 'refs/heads/main'
        run: |
          npx speak-mintlify generate . \
            --voices "8ef4a238714b45718ce04243307c57a7" \
            --voice-names "Preview Voice"
        env:
          FISH_API_KEY: ${{ secrets.FISH_API_KEY }}
          # ... other env vars

Incremental Updates

Only process changed files:
.github/workflows/tts-incremental.yaml
name: Incremental TTS

on:
  push:
    paths:
      - '**.mdx'

jobs:
  generate:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
        with:
          fetch-depth: 2

      - uses: actions/setup-node@v4

      - name: Get changed files
        id: changed
        run: |
          echo "files=$(git diff --name-only HEAD~1 HEAD | grep '.mdx$' | tr '\n' ' ')" >> $GITHUB_OUTPUT

      - name: Generate for changed files
        if: steps.changed.outputs.files != ''
        run: |
          for file in ${{ steps.changed.outputs.files }}; do
            npx speak-mintlify generate . --pattern "$file"
          done
        env:
          FISH_API_KEY: ${{ secrets.FISH_API_KEY }}
          # ... other env vars
speak-mintlify already tracks content hashes and only regenerates when content changes. This workflow is for advanced use cases with large documentation sets.

Multi-Environment Setup

Use different configurations for staging and production:
speaker-config.dev.yaml
voices:
  8ef4a238714b45718ce04243307c57a7: Dev Voice

component:
  import: /snippets/audio-dev.jsx
  name: DevAudio
# Copy to speaker-config.yaml for local testing
cp speaker-config.dev.yaml speaker-config.yaml
npx speak-mintlify generate . --dry-run

Styling and Theming

Tailwind CSS Integration

The default component uses Tailwind. Customize with your theme:
/snippets/audio-transcript.jsx
// Use your custom Tailwind classes
<div className="border rounded-lg bg-card border-primary/20">
  <div className="px-3 py-1.5 bg-primary/10">
    <span className="text-sm font-semibold text-primary">Listen</span>
  </div>
  {/* ... */}
</div>

CSS Modules

For scoped styles:
/snippets/styled-audio.jsx
import styles from './audio.module.css';

export const StyledAudio = ({ voices = [] }) => {
  return (
    <div className={styles.audioPlayer}>
      <audio controls src={voices[0]?.url} className={styles.audioElement} />
    </div>
  );
};
/snippets/audio.module.css
.audioPlayer {
  padding: 1rem;
  border: 2px solid var(--accent-color);
  border-radius: 0.5rem;
  background: var(--bg-color);
}

.audioElement {
  width: 100%;
  accent-color: var(--accent-color);
}

Dark Mode Support

Handle dark mode with Tailwind:
<div className="bg-white dark:bg-gray-900 text-gray-900 dark:text-gray-100">
  <audio
    controls
    className="w-full [&::-webkit-media-controls-panel]:bg-gray-100 dark:[&::-webkit-media-controls-panel]:bg-gray-800"
    src={voices[0]?.url}
  />
</div>

Troubleshooting Customization

  • Check import path is correct in speaker-config.yaml
  • Verify file exists at specified path
  • Ensure component is exported (named export)
  • Check component name matches exactly (case-sensitive)
  • Verify component accepts voices prop
  • Check prop structure matches interface
  • Use --verbose to see generated component code
  • Test component independently first
  • Ensure Tailwind processes your component file
  • Check CSS imports are working
  • Verify class names don’t conflict
  • Test in browser dev tools
  • Test pattern with glob tool: npx glob "your-pattern"
  • Ensure pattern is quoted in CLI
  • Check .speakignore isn’t excluding files
  • Use --verbose to see matched files

Best Practices

1

Start with the default component

Modify the provided audio-transcript.jsx rather than building from scratch.
2

Test locally first

Use --dry-run to preview changes before generating audio.
3

Keep components simple

Focus on core functionality. Complex interactions can impact performance.
4

Support accessibility

Include ARIA labels, keyboard navigation, and screen reader support.
5

Optimize for mobile

Ensure audio player works well on touch devices.
6

Version your components

Track component changes in git for easy rollback.

Next Steps

CI/CD Integration

Automate customized workflows

Fish Audio

Configure voices for your custom player

Build docs developers (and LLMs) love