Back to Home

Customization Guide

Tipex is architected with customization as a core principle. Every aspect of the editor can be tailored to match your application's design system and functional requirements. This comprehensive guide covers everything from basic styling to advanced extension development.

Quick Customization Overview

Tipex offers multiple layers of customization:

  • Theme Customization: Colors, spacing, typography, and visual styling
  • Component Replacement: Replace built-in controls with your own components
  • Extension System: Add new functionality through TipTap extensions
  • Event Handling: Custom behavior through event listeners
  • Layout Customization: Header, footer, and utility area modifications

Theme & Styling Customization

Tipex uses Tailwind CSS v4 with the modern `@theme` configuration system and CSS custom properties for comprehensive theming. You can override any aspect of the visual design using the new Tailwind v4 architecture:

/* Modern Tailwind v4 Theme Configuration - Based on Tipex Design System */
@import "tailwindcss";

@theme {
  /* Override Tipex Design System Colors */
  --color-tipex-*: initial;
  
  /* Tipex Grayscale Colors (50-950) - Override defaults */
  --color-tipex-50: oklch(0.99 0 0);
  --color-tipex-100: oklch(0.975 0 0);
  --color-tipex-200: oklch(0.93 0 0);
  --color-tipex-300: oklch(0.88 0 0);
  --color-tipex-400: oklch(0.72 0 0);
  --color-tipex-500: oklch(0.57 0 0);
  --color-tipex-600: oklch(0.45 0 0);
  --color-tipex-700: oklch(0.38 0 0);
  --color-tipex-800: oklch(0.28 0 0);
  --color-tipex-900: oklch(0.22 0 0);
  --color-tipex-950: oklch(0.15 0 0);
  
  /* Tipex Success Colors (Green) - Override defaults */
  --color-tipex-success-50: oklch(0.985 0.008 140);
  --color-tipex-success-100: oklch(0.972 0.015 140);
  --color-tipex-success-200: oklch(0.93 0.045 140);
  --color-tipex-success-300: oklch(0.875 0.09 140);
  --color-tipex-success-400: oklch(0.775 0.16 140);
  --color-tipex-success-500: oklch(0.65 0.21 140);
  --color-tipex-success-600: oklch(0.55 0.21 140);
  --color-tipex-success-700: oklch(0.47 0.185 140);
  --color-tipex-success-800: oklch(0.39 0.15 140);
  --color-tipex-success-900: oklch(0.33 0.12 140);
  --color-tipex-success-950: oklch(0.21 0.08 140);
  
  /* Tipex Primary Colors (Indigo) - Override defaults */
  --color-tipex-primary-50: oklch(0.985 0.004 270);
  --color-tipex-primary-100: oklch(0.972 0.008 270);
  --color-tipex-primary-200: oklch(0.93 0.022 270);
  --color-tipex-primary-300: oklch(0.875 0.045 270);
  --color-tipex-primary-400: oklch(0.775 0.08 270);
  --color-tipex-primary-500: oklch(0.65 0.1 270);
  --color-tipex-primary-600: oklch(0.55 0.1 270);
  --color-tipex-primary-700: oklch(0.47 0.085 270);
  --color-tipex-primary-800: oklch(0.39 0.07 270);
  --color-tipex-primary-900: oklch(0.33 0.058 270);
  --color-tipex-primary-950: oklch(0.21 0.045 270);
  
  /* Spacing Scale - Override defaults */
  --spacing-tipex-xs: 0.25rem;
  --spacing-tipex-sm: 0.5rem;
  --spacing-tipex-md: 1rem;
  --spacing-tipex-lg: 1.5rem;
  --spacing-tipex-xl: 2rem;
  --spacing-tipex-2xl: 2.5rem;
  
  /* Sizing Scale - Override defaults */
  --size-tipex-1: 0.25rem;
  --size-tipex-2: 0.5rem;
  --size-tipex-3: 0.75rem;
  --size-tipex-4: 1rem;
  --size-tipex-5: 1.25rem;
  --size-tipex-6: 1.5rem;
  --size-tipex-7: 1.75rem;
  --size-tipex-8: 2rem;
  --size-tipex-9: 2.25rem;
  --size-tipex-10: 2.5rem;
  --size-tipex-11: 2.75rem;
  --size-tipex-12: 3rem;
  
  /* Border Radius - Override defaults */
  --radius-tipex-sm: 0.25rem;
  --radius-tipex-md: 0.375rem;
  
  /* Typography Scale - Override defaults */
  --text-tipex-xs: 0.75rem;
  --text-tipex-sm: 0.875rem;
  --text-tipex-base: 1rem;
  --text-tipex-lg: 1.125rem;
  --text-tipex-xl: 1.25rem;
  --text-tipex-2xl: 1.5rem;
  
  /* Z-Index - Override defaults */
  --z-tipex-floating: 50;
  --z-tipex-controls: 10;
  
  /* Custom additions (not in default theme) */
  --shadow-tipex-sm: 0 1px 2px 0 rgb(0 0 0 / 0.05);
  --shadow-tipex-md: 0 4px 6px -1px rgb(0 0 0 / 0.1);
  --shadow-tipex-focus: 0 0 0 3px rgb(139 92 246 / 0.5);
}

/* Custom Dark Mode Variant (Tailwind v4) */
@custom-variant dark (&:where(.dark, .dark *));

/* Component Layer Customization */
@layer components {
  .tipex-editor {
    @apply bg-tipex-50 dark:bg-tipex-950
           border border-tipex-200 dark:border-tipex-800
           rounded-radius-tipex-md;
  }
  
  .tipex-editor.focused.focal {
    @apply shadow-tipex-focus
           border-tipex-primary-500 dark:border-tipex-primary-400
           outline-none;
  }
  
  .tipex-controls {
    @apply bg-tipex-100 dark:bg-tipex-900
           border border-tipex-200 dark:border-tipex-800
           rounded-radius-tipex-sm;
  }
}

Control Component Customization

Tipex provides a single, flexible way to customize the editor controls through the `controlComponent` slot. You can either extend the default controls with custom utilities or completely replace them with your own implementation.

Tipex Editor control customization options
Control component provides complete flexibility for editor customization

Using Default Controls with Built-in Utilities

Include the default controls and utility buttons (copy, link management) in your editor:

import { Tipex, Controls } from "@friendofsvelte/tipex";

<Tipex body={body}>
  {#snippet controlComponent(tipex)}
    <Controls {tipex}>
      <!-- Built-in utilities: copy HTML and link editing with clipboard integration -->
      <!-- Additional utility buttons can be added here -->
      <button class="tipex-edit-button tipex-button-rigid" aria-label="Custom action">
        <ICONHERE />
      </button>
    </Controls>
  {/snippet}
</Tipex>

Extending Default Controls with Custom Utilities

Add your own custom buttons alongside the built-in utilities:

<Tipex body={body}>
  {#snippet controlComponent(tipex)}
    <Controls {tipex}>
      <!-- Built-in utilities: copy HTML and link editing with clipboard integration -->
      <!-- Add custom utilities here -->
      <button class="tipex-edit-button tipex-button-rigid" aria-label="Export PDF">
        <ICONHERE />
      </button>
    </Controls>
  {/snippet}
</Tipex>

Here's a more comprehensive example with multiple custom utilities:

<Tipex body={body}>
  {#snippet controlComponent(tipex)}
    <Controls {tipex}>
      <!-- Built-in utilities: copy HTML and link editing with clipboard integration -->
      <!-- Add your custom utilities here -->
      <button 
        class="tipex-edit-button tipex-button-rigid"
        onclick={() => exportToPDF(tipex.getHTML())}
        aria-label="Export to PDF"
      >
        <ICONHERE />
      </button>
      
      <button 
        class="tipex-edit-button tipex-button-rigid"
        onclick={() => insertTemplate(tipex)}
        aria-label="Insert template"
      >
        <ICONHERE />
      </button>
      
      <button 
        class="tipex-edit-button tipex-button-rigid"
        onclick={() => toggleFullscreen()}
        aria-label="Toggle fullscreen"
      >
        <ICONHERE />
      </button>
    </Controls>
  {/snippet}
</Tipex>

Tipex Props

Prop NameDefaultConditionDescription
head(tipex)undefinedOptionalA slot that accepts a function receiving the TipexEditor instance, rendered above the main editor content area
controlComponentSnippet<[TipexEditor]> | nullOptionalA slot that accepts a function receiving the TipexEditor instance, used to completely replace the default controls. Set to null to hide all controls completely.
foot(tipex)undefinedOptionalA slot that accepts a function receiving the TipexEditor instance, rendered below the editor content and controls

Control System

Tipex automatically detects which control system to use:

  • When controlComponent is provided: Uses your custom control component
  • When controlComponent is not provided: Shows default controls with built-in utilities
  • When controlComponent={null}: Hides all controls completely (no toolbar)

This ensures a clean API where you don't need to manage boolean flags.

Complete Control Replacement

Replace the entire control system with your own custom implementation:

<Tipex body={body}>
  {#snippet controlComponent(tipex)}
    <div aria-label="New Custom Control">...</div>
  {/snippet}
</Tipex>

Here's a complete custom control implementation:

<script>
  import CustomToolbar from './CustomToolbar.svelte';
</script>

<Tipex body={body}>
  {#snippet controlComponent(tipex)}
    <CustomToolbar {tipex} />
  {/snippet}
</Tipex>

<!-- CustomToolbar.svelte -->
<script>
  export let tipex;
  
  let isActive = $derived((name, attrs = {}) => tipex?.isActive(name, attrs) ?? false);
  let canExecute = $derived((command) => tipex?.can()[command]() ?? false);
</script>

<div class="custom-toolbar">
  <div class="toolbar-section">
    <button 
      class="toolbar-btn"
      class:active={isActive('bold')}
      disabled={!canExecute('toggleBold')}
      onclick={() => tipex.chain().focus().toggleBold().run()}
    >
      <strong>B</strong>
    </button>
    
    <button 
      class="toolbar-btn"
      class:active={isActive('italic')}
      onclick={() => tipex.chain().focus().toggleItalic().run()}
    >
      <em>I</em>
    </button>
  </div>
  
  <div class="toolbar-section">
    <select 
      value={isActive('heading', {level: 1}) ? '1' : 
             isActive('heading', {level: 2}) ? '2' : 'p'}
      onchange={(e) => {
        const level = e.target.value;
        if (level === 'p') {
          tipex.chain().focus().setParagraph().run();
        } else {
          tipex.chain().focus().toggleHeading({level: parseInt(level)}).run();
        }
      }}
    >
      <option value="p">Paragraph</option>
      <option value="1">Heading 1</option>
      <option value="2">Heading 2</option>
    </select>
  </div>
</div>

<style>
	@reference "../app.css";
    .custom-toolbar {
      @apply flex gap-spacing-tipex-md p-spacing-tipex-lg
           bg-tipex-100 dark:bg-tipex-900
           rounded-radius-tipex-md
           border border-tipex-200 dark:border-tipex-800;
    }
    
    .toolbar-btn {
      @apply px-spacing-tipex-sm py-spacing-tipex-xs
             bg-tipex-50 dark:bg-tipex-950
             border border-tipex-200 dark:border-tipex-800
             rounded-radius-tipex-sm
             hover:bg-tipex-100 dark:hover:bg-tipex-800
             focus:outline-none focus:ring-2 focus:ring-tipex-primary-500/40;
    }
    
    .toolbar-btn.active {
      @apply bg-tipex-primary-100 dark:bg-tipex-primary-900
             border-tipex-primary-300 dark:border-tipex-primary-700
             text-tipex-primary-700 dark:text-tipex-primary-300;
    }
</style>

⚠️ Tailwind v4 ONLY - No Legacy Versions!

IMPORTANT: Tipex exclusively uses and requires Tailwind CSS v4. We do NOT support older versions (v1.x, v2.x, v3.x) as they lack the modern architecture required for Tipex's advanced theming system.

Legacy Tailwind Versions Not Supported

  • Tailwind v3.x and older lack @theme configuration
  • Missing @custom-variant syntax for advanced dark mode
  • Inferior performance and larger bundle sizes
  • Limited component layer organization

Tailwind v4 Advantages

Tipex exclusively uses Tailwind CSS v4 for superior developer experience and performance:

  • 🚀 Modern Import System: Clean `@import "tailwindcss"` syntax without configuration files
  • 🎨 Native CSS Custom Properties: Direct integration with CSS variables in `@theme` blocks
  • ⚡ Better Performance: Faster builds and smaller bundle sizes compared to v3.x
  • 🛠️ Enhanced DX: Improved IntelliSense and better error messages
  • 🌙 Custom Variants: Powerful `@custom-variant` syntax for complex state handling
  • 📦 Component Layers: Better organization with `@layer components` for maintainable styles
  • 🔮 Future-Proof: Built for modern CSS features and browser capabilities

Tailwind v4 Required

Upgrade to Tailwind v4 immediately for the best Tipex experience. The new architecture is essential for Tipex's theming system to function properly.

Migration Note: If you're using older Tailwind versions (v1.x, v2.x, v3.x), you MUST upgrade to Tailwind v4 for Tipex to work correctly. The new architecture provides significantly better performance, developer experience, and maintainability.

Advanced Layout Customization

Tipex supports comprehensive layout customization through multiple slot areas:

<script lang="ts">
  import { Tipex } from "@friendofsvelte/tipex";
  import type { Editor } from '@tiptap/core';
  import DocumentHeader from "./DocumentHeader.svelte";
  import DocumentFooter from "./DocumentFooter.svelte";
  import CustomToolbar from "./CustomToolbar.svelte";
  
  let editor = $state<Editor>();
  let wordCount = $state(0);
  let lastSaved = $state<Date | null>(null);
  
  // Reactive word count using runes
  $effect(() => {
    if (editor) {
      const text = editor.getText();
      wordCount = text.split(/\s+/).filter(word => word.length > 0).length;
    }
  });
  
  // Auto-save functionality using runes
  $effect(() => {
    if (editor) {
      const debounceTimer = setTimeout(() => {
        saveDocument(editor.getHTML());
        lastSaved = new Date();
      }, 2000);
      
      return () => clearTimeout(debounceTimer);
    }
  });
  
  async function saveDocument(content: string) {
    try {
      await fetch('/api/documents/save', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({ content, timestamp: new Date().toISOString() })
      });
    } catch (error) {
      console.error('Auto-save failed:', error);
    }
  }
</script>

<Tipex bind:tipex={editor} body={body}>
  {#snippet head(tipex)}
    <DocumentHeader 
      {wordCount} 
      {lastSaved}
      characterCount={tipex.getText().length}
      readingTime={Math.ceil(wordCount / 200)}
    />
  {/snippet}
  
  {#snippet controlComponent(tipex)}
    <CustomToolbar 
      {tipex} 
      onSave={() => saveDocument(tipex.getHTML())}
      onExport={() => exportDocument(tipex.getHTML())}
    />
  {/snippet}
  
  {#snippet foot(tipex)}
    <DocumentFooter 
      status={lastSaved ? 'saved' : 'unsaved'}
      {wordCount}
      version="1.2.3"
    />
  {/snippet}
</Tipex>

Here's a practical example with a custom header featuring document statistics:

<script>
  import { Tipex } from "@friendofsvelte/tipex";
  import DocumentStats from "./DocumentStats.svelte";
  import CustomControl from "./CustomControl.svelte";
  import SaveIndicator from "./SaveIndicator.svelte";
  
  let editor = $state();
  let lastSaved = $state(null);
  
  // Auto-save functionality using runes
  $effect(() => {
    if (editor) {
      const debounce = setTimeout(() => {
        saveDocument(editor.getHTML());
        lastSaved = new Date();
      }, 1000);
      
      return () => clearTimeout(debounce);
    }
  });
</script>

<Tipex bind:tipex={editor} body={body}>
  {#snippet head(tipex)}
    <div class="editor-header">
      <DocumentStats {tipex} />
      <SaveIndicator {lastSaved} />
    </div>
  {/snippet}
  
  {#snippet controlComponent(tipex)}
    <CustomControl {tipex} />
  {/snippet}
  
  {#snippet foot(tipex)}
    <div class="editor-footer">
      <span class="status">Ready</span>
      <span class="word-count">{getWordCount(tipex)} words</span>
    </div>
  {/snippet}
</Tipex>

Image Upload Customization

Tipex provides flexible image handling. You can customize the upload process, validation, and storage:

<script>
  import { Tipex } from "@friendofsvelte/tipex";
  
  async function handleImageUpload(file) {
    // Custom validation
    const maxSize = 5 * 1024 * 1024; // 5MB
    const allowedTypes = ['image/jpeg', 'image/png', 'image/webp'];
    
    if (!allowedTypes.includes(file.type)) {
      throw new Error('Unsupported file type');
    }
    
    if (file.size > maxSize) {
      throw new Error('File too large');
    }
    
    // Upload to your service (Cloudinary, AWS S3, etc.)
    const formData = new FormData();
    formData.append('image', file);
    
    const response = await fetch('/api/upload', {
      method: 'POST',
      body: formData
    });
    
    if (!response.ok) throw new Error('Upload failed');
    
    const { url, publicId } = await response.json();
    
    return {
      src: url,
      alt: file.name,
      'data-public-id': publicId // For later deletion
    };
  }
  
  let editor;
</script>

<Tipex bind:tipex={editor} body={body}>
  {#snippet controlComponent(tipex)}
    <div class="custom-image-controls">
      <!-- Built-in controls would go here if using Controls component -->
      <button 
        class="tipex-edit-button tipex-button-rigid"
        onclick={() => {
          const input = document.createElement('input');
          input.type = 'file';
          input.accept = 'image/*';
          input.onchange = async (e) => {
            const file = e.target.files[0];
            if (file) {
              try {
                const imageData = await handleImageUpload(file);
                tipex.commands.setImage(imageData);
              } catch (error) {
                alert('Upload failed: ' + error.message);
              }
            }
          };
          input.click();
        }}
        aria-label="Upload image"
      >
        📷 Upload Image
      </button>
      
      <button 
        class="tipex-edit-button tipex-button-rigid"
        onclick={() => tipex.commands.toggleBold()}
        aria-label="Toggle bold"
      >
        <strong>B</strong>
      </button>
      
      <button 
        class="tipex-edit-button tipex-button-rigid"
        onclick={() => tipex.commands.toggleItalic()}
        aria-label="Toggle italic"
      >
        <em>I</em>
      </button>
    </div>
  {/snippet}
</Tipex>

Extension System

Leverage TipTap's powerful extension system to add custom functionality. Tipex provides easy access to modify and extend the editor's capabilities:

Advanced customization example showing custom extensions and theming in Tipex Editor
Advanced customization with custom extensions and theming. View Source Code

Modifying Default Extensions

Customize the built-in extensions to match your requirements:

<script lang="ts">
  import { defaultExtensions, Tipex } from "@friendofsvelte/tipex";
  import { Heading } from '@tiptap/extension-heading';
  import { TextAlign } from '@tiptap/extension-text-align';
  import { Highlight } from '@tiptap/extension-highlight';
  import { Underline } from '@tiptap/extension-underline';
  import { Table } from '@tiptap/extension-table';
  import { TableRow } from '@tiptap/extension-table-row';
  import { TableHeader } from '@tiptap/extension-table-header';
  import { TableCell } from '@tiptap/extension-table-cell';
  import type { Extensions } from '@tiptap/core';
  
  // Custom extension configuration
  const customExtensions: Extensions = [
    ...defaultExtensions.filter(ext => ext.name !== 'heading'), // Remove default heading
    
    // Custom heading configuration
    Heading.configure({
      levels: [1, 2, 3, 4], // Only allow H1-H4
      HTMLAttributes: {
        class: 'custom-heading',
      },
    }),
    
    // Text alignment support
    TextAlign.configure({
      types: ['heading', 'paragraph'],
      alignments: ['left', 'center', 'right', 'justify'],
      defaultAlignment: 'left',
    }),
    
    // Highlighting with multiple colors
    Highlight.configure({
      multicolor: true,
      HTMLAttributes: {
        class: 'custom-highlight',
      },
    }),
    
    // Underline support
    Underline.configure({
      HTMLAttributes: {
        class: 'custom-underline',
      },
    }),
    
    // Table support
    Table.configure({
      resizable: true,
      HTMLAttributes: {
        class: 'custom-table',
      },
    }),
    TableRow,
    TableHeader,
    TableCell,
  ];
  
  let body = `
    <h1>Welcome to Enhanced Tipex</h1>
    <p>This editor now supports:</p>
    <ul>
      <li>Custom heading levels (H1-H4 only)</li>
      <li>Text alignment options</li>
      <li><mark data-color="#ffeb3b">Multi-color highlighting</mark></li>
      <li><u>Underlined text</u></li>
      <li>Resizable tables</li>
    </ul>
  `;
</script>

<Tipex extensions={customExtensions} {body} />

Creating Custom Extensions

Build your own extensions for specialized functionality:

<script>
  import { Extension } from '@tiptap/core';
  import { defaultExtensions, Tipex } from "@friendofsvelte/tipex";
  
  // Custom extension for highlighting text
  const Highlight = Extension.create({
    name: 'highlight',
    
    addOptions() {
      return {
        multicolor: true,
        HTMLAttributes: {},
      };
    },
    
    addGlobalAttributes() {
      return [
        {
          types: ['textStyle'],
          attributes: {
            backgroundColor: {
              default: null,
              parseHTML: element => element.style.backgroundColor,
              renderHTML: attributes => {
                if (!attributes.backgroundColor) return {};
                return { style: `background-color: ${attributes.backgroundColor}` };
              },
            },
          },
        },
      ];
    },
    
    addCommands() {
      return {
        setHighlight: (attributes) => ({ commands }) => {
          return commands.setMark('textStyle', attributes);
        },
        toggleHighlight: (attributes) => ({ commands }) => {
          return commands.toggleMark('textStyle', attributes);
        },
        unsetHighlight: () => ({ commands }) => {
          return commands.unsetMark('textStyle');
        },
      };
    },
  });
  
  // Custom extension for word count
  const WordCount = Extension.create({
    name: 'wordCount',
    
    addStorage() {
      return {
        wordCount: 0,
        characterCount: 0,
      };
    },
    
    onUpdate() {
      const text = this.editor.getText();
      this.storage.wordCount = text.split(/\\s+/).filter(word => word.length > 0).length;
      this.storage.characterCount = text.length;
    },
  });
  
  const customExtensions = [
    ...defaultExtensions,
    Highlight,
    WordCount,
    // Add more custom extensions
  ];
</script>

<Tipex extensions={customExtensions} body={body}>
  {#snippet controlComponent(tipex)}
    <div class="custom-controls">
      <!-- Highlight controls -->
      <button 
        onclick={() => tipex.commands.setHighlight({ backgroundColor: '#ffeb3b' })}
        class="tipex-edit-button"
      >
        <ICONHERE />
      </button>
      
      <!-- Word count display -->
      <span class="word-count">
        Words: {tipex.storage.wordCount?.wordCount || 0}
      </span>
    </div>
  {/snippet}
</Tipex>

Event-Driven Customization

React to editor events for dynamic behavior and integrations:

<script>
  import { Tipex } from "@friendofsvelte/tipex";
  
  let editor = $state();
  let isTyping = $state(false);
  let typingTimer;
  
  function handleEditorReady(tipex) {
    // Auto-save setup
    tipex.on('update', ({ editor }) => {
      clearTimeout(typingTimer);
      isTyping = true;
      
      typingTimer = setTimeout(() => {
        isTyping = false;
        saveContent(editor.getHTML());
      }, 1000);
    });
    
    // Custom keyboard shortcuts
    tipex.on('keydown', (event) => {
      // Ctrl+S for save
      if (event.ctrlKey && event.key === 's') {
        event.preventDefault();
        saveContent(tipex.getHTML());
      }
      
      // Ctrl+Shift+L for link
      if (event.ctrlKey && event.shiftKey && event.key === 'L') {
        event.preventDefault();
        const url = prompt('Enter URL:');
        if (url) {
          tipex.commands.setLink({ href: url });
        }
      }
    });
    
    // Selection change handling
    tipex.on('selectionUpdate', ({ editor }) => {
      updateFloatingMenu(editor);
    });
    
    // Focus/blur handling
    tipex.on('focus', () => {
      document.body.classList.add('editor-focused');
    });
    
    tipex.on('blur', () => {
      document.body.classList.remove('editor-focused');
    });
  }
  
  async function saveContent(html) {
    try {
      await fetch('/api/save', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({ content: html })
      });
    } catch (error) {
      console.error('Save failed:', error);
    }
  }
</script>

<Tipex 
  bind:tipex={editor}
  body={body}
  onready={handleEditorReady}
>
  {#snippet head()}
    <div class="editor-status">
      {#if isTyping}
        <span class="status typing">Typing...</span>
      {:else}
        <span class="status saved">Saved</span>
      {/if}
    </div>
  {/snippet}
</Tipex>

Accessibility Customization

Enhance accessibility with custom ARIA labels, keyboard navigation, and screen reader support:

<script>
  function enhanceAccessibility(tipex) {
    // Add custom ARIA labels
    const editorElement = tipex.view.dom;
    editorElement.setAttribute('aria-label', 'Rich text editor');
    editorElement.setAttribute('role', 'textbox');
    editorElement.setAttribute('aria-multiline', 'true');
    
    // Custom keyboard shortcuts for accessibility
    tipex.on('keydown', (event) => {
      // Alt+1-6 for headings
      if (event.altKey && event.key >= '1' && event.key <= '6') {
        event.preventDefault();
        const level = parseInt(event.key);
        tipex.commands.toggleHeading({ level });
        
        // Announce to screen readers
        announceToScreenReader(`Heading level ${level} applied`);
      }
      
      // Alt+L for list
      if (event.altKey && event.key === 'l') {
        event.preventDefault();
        tipex.commands.toggleBulletList();
        announceToScreenReader('Bullet list toggled');
      }
    });
  }
  
  function announceToScreenReader(message) {
    const announcement = document.createElement('div');
    announcement.setAttribute('aria-live', 'polite');
    announcement.setAttribute('aria-atomic', 'true');
    announcement.className = 'sr-only';
    announcement.textContent = message;
    
    document.body.appendChild(announcement);
    setTimeout(() => document.body.removeChild(announcement), 1000);
  }
</script>

<Tipex 
  body={body}
  onready={enhanceAccessibility}
  aria-label="Document editor"
>
  {#snippet controlComponent(tipex)}
    <div class="accessible-controls" role="toolbar" aria-label="Formatting options">
      <button 
        class="tipex-edit-button"
        class:active={tipex.isActive('bold')}
        onclick={() => tipex.commands.toggleBold()}
        aria-label="Toggle bold formatting"
        aria-pressed={tipex.isActive('bold')}
      >
        <strong>B</strong>
      </button>
      
      <button 
        class="tipex-edit-button"
        class:active={tipex.isActive('italic')}
        onclick={() => tipex.commands.toggleItalic()}
        aria-label="Toggle italic formatting"
        aria-pressed={tipex.isActive('italic')}
      >
        <em>I</em>
      </button>
    </div>
  {/snippet}
</Tipex>

<style>
  .sr-only {
    position: absolute;
    width: 1px;
    height: 1px;
    padding: 0;
    margin: -1px;
    overflow: hidden;
    clip: rect(0, 0, 0, 0);
    white-space: nowrap;
    border: 0;
  }
</style>

Performance Optimization with Tailwind v4

Optimize your customized editor using Tailwind v4's performance benefits:

  • 🎯 Tailwind v4 Tree Shaking: Automatic unused CSS elimination
  • ⚡ Faster Builds: Improved build performance over legacy versions
  • 📦 Smaller Bundles: More efficient CSS generation
  • 🔄 Lazy Loading: Load extensions and components only when needed
  • ⏱️ Debounced Updates: Throttle auto-save and real-time features
  • 🖥️ Virtual Scrolling: For large documents, implement virtual scrolling
  • 🧠 Memory Management: Clean up event listeners and subscriptions

Tailwind v4 Performance Benefits

Tailwind v4's modern architecture provides up to 50% faster builds and 30% smaller CSS bundles compared to v3.x, making your Tipex editor load faster and perform better.

Best Practices with Tailwind v4: When customizing Tipex, leverage Tailwind v4's `@layer components`, `@theme` configuration, and `@custom-variant` features. Always test your changes across different devices and browsers. The modern Tailwind v4 architecture ensures better performance and maintainability compared to legacy versions.

Advanced Tailwind v4 Theming

Create sophisticated themes using Tailwind v4's advanced features. Here's a complete example of a premium theme configuration:

/* Premium Tipex Theme with Tailwind v4 - Using Actual Design System */
@import "tailwindcss";

@theme {
  /* Override Tipex Design System with Premium Colors */
  --color-tipex-*: initial;
  
  /* Premium Grayscale Palette (OKLCH for better color accuracy) */
  --color-tipex-50: oklch(0.99 0 0);
  --color-tipex-100: oklch(0.975 0 0);
  --color-tipex-200: oklch(0.925 0 0);
  --color-tipex-300: oklch(0.875 0 0);
  --color-tipex-400: oklch(0.715 0 0);
  --color-tipex-500: oklch(0.565 0 0);
  --color-tipex-600: oklch(0.445 0 0);
  --color-tipex-700: oklch(0.375 0 0);
  --color-tipex-800: oklch(0.275 0 0);
  --color-tipex-900: oklch(0.215 0 0);
  --color-tipex-950: oklch(0.15 0 0);
  
  /* Premium Success Colors (Emerald Green) */
  --color-tipex-success-50: oklch(0.985 0.008 145);
  --color-tipex-success-100: oklch(0.972 0.015 145);
  --color-tipex-success-200: oklch(0.93 0.045 145);
  --color-tipex-success-300: oklch(0.875 0.09 145);
  --color-tipex-success-400: oklch(0.775 0.16 145);
  --color-tipex-success-500: oklch(0.65 0.21 145);
  --color-tipex-success-600: oklch(0.55 0.21 145);
  --color-tipex-success-700: oklch(0.47 0.185 145);
  --color-tipex-success-800: oklch(0.39 0.15 145);
  --color-tipex-success-900: oklch(0.33 0.12 145);
  --color-tipex-success-950: oklch(0.21 0.08 145);
  
  /* Premium Primary Colors (Purple/Violet) */
  --color-tipex-primary-50: oklch(0.985 0.005 285);
  --color-tipex-primary-100: oklch(0.972 0.01 285);
  --color-tipex-primary-200: oklch(0.93 0.025 285);
  --color-tipex-primary-300: oklch(0.875 0.05 285);
  --color-tipex-primary-400: oklch(0.775 0.085 285);
  --color-tipex-primary-500: oklch(0.65 0.11 285);
  --color-tipex-primary-600: oklch(0.55 0.11 285);
  --color-tipex-primary-700: oklch(0.47 0.095 285);
  --color-tipex-primary-800: oklch(0.39 0.08 285);
  --color-tipex-primary-900: oklch(0.33 0.065 285);
  --color-tipex-primary-950: oklch(0.21 0.05 285);
  
  /* Enhanced Spacing Scale */
  --spacing-tipex-xs: 0.375rem;
  --spacing-tipex-sm: 0.625rem;
  --spacing-tipex-md: 1.125rem;
  --spacing-tipex-lg: 1.75rem;
  --spacing-tipex-xl: 2.25rem;
  --spacing-tipex-2xl: 2.75rem;
  
  /* Enhanced Sizing Scale */
  --size-tipex-1: 0.375rem;
  --size-tipex-2: 0.625rem;
  --size-tipex-3: 0.875rem;
  --size-tipex-4: 1.125rem;
  --size-tipex-5: 1.375rem;
  --size-tipex-6: 1.625rem;
  --size-tipex-7: 1.875rem;
  --size-tipex-8: 2.125rem;
  --size-tipex-9: 2.375rem;
  --size-tipex-10: 2.625rem;
  --size-tipex-11: 2.875rem;
  --size-tipex-12: 3.125rem;
  
  /* Enhanced Border Radius */
  --radius-tipex-sm: 0.375rem;
  --radius-tipex-md: 0.5rem;
  --radius-tipex-lg: 0.75rem;
  
  /* Enhanced Typography */
  --text-tipex-xs: 0.8125rem;
  --text-tipex-sm: 0.9375rem;
  --text-tipex-base: 1.0625rem;
  --text-tipex-lg: 1.1875rem;
  --text-tipex-xl: 1.375rem;
  --text-tipex-2xl: 1.625rem;
  
  /* Enhanced Z-Index */
  --z-tipex-floating: 100;
  --z-tipex-controls: 50;
  
  /* Premium Shadows */
  --shadow-tipex-sm: 0 1px 3px 0 rgb(0 0 0 / 0.08), 0 1px 2px 0 rgb(0 0 0 / 0.06);
  --shadow-tipex-md: 0 4px 8px -2px rgb(0 0 0 / 0.12), 0 2px 4px -2px rgb(0 0 0 / 0.06);
  --shadow-tipex-lg: 0 8px 16px -4px rgb(0 0 0 / 0.16), 0 4px 8px -4px rgb(0 0 0 / 0.08);
  --shadow-tipex-focus: 0 0 0 3px oklch(0.65 0.11 285 / 0.4);
}

/* Custom Dark Mode Variant */
@custom-variant dark (&:where(.dark, .dark *));

/* Premium Component Styling */
@layer components {
  .tipex-editor-premium {
    @apply bg-gradient-to-br from-tipex-50 via-tipex-100 to-tipex-200
           dark:from-tipex-950 dark:via-tipex-900 dark:to-tipex-800
           border-2 border-tipex-200 dark:border-tipex-800
           rounded-radius-tipex-lg shadow-tipex-md;
  }
  
  .tipex-editor-premium.focused.focal {
    @apply shadow-tipex-focus
           border-tipex-primary-500 dark:border-tipex-primary-400
           outline-none ring-2 ring-tipex-primary-500/20 dark:ring-tipex-primary-400/20;
  }
  
  .tipex-control-premium {
    @apply bg-tipex-100/90 dark:bg-tipex-900/90
           backdrop-blur-sm border border-tipex-200/50 dark:border-tipex-800/50
           rounded-radius-tipex-md shadow-tipex-sm;
  }
  
  .tipex-button-premium {
    @apply px-spacing-tipex-md py-spacing-tipex-sm
           bg-gradient-to-b from-tipex-100 to-tipex-200
           dark:from-tipex-900 dark:to-tipex-800
           border border-tipex-300 dark:border-tipex-700
           rounded-radius-tipex-sm shadow-tipex-sm
           hover:shadow-tipex-md hover:scale-105
           active:scale-95 active:shadow-tipex-sm
           transition-all duration-150 ease-out
           focus:outline-none focus:ring-2 focus:ring-tipex-primary-500/40;
  }
  
  .tipex-button-premium.active {
    @apply bg-gradient-to-b from-tipex-primary-100 to-tipex-primary-200
           dark:from-tipex-primary-900 dark:to-tipex-primary-800
           border-tipex-primary-500 dark:border-tipex-primary-400
           text-tipex-primary-700 dark:text-tipex-primary-300
           shadow-tipex-md ring-2 ring-tipex-primary-500/20 dark:ring-tipex-primary-400/20;
  }
  
  .tipex-scrollbar-premium {
    scrollbar-width: thin;
    scrollbar-color: oklch(0.65 0.11 285 / 0.3) transparent;
  }
  
  .tipex-scrollbar-premium::-webkit-scrollbar {
    width: 6px;
  }
  
  .tipex-scrollbar-premium::-webkit-scrollbar-track {
    background: transparent;
  }
  
  .tipex-scrollbar-premium::-webkit-scrollbar-thumb {
    background: linear-gradient(to bottom, 
      oklch(0.65 0.11 285 / 0.3), 
      oklch(0.55 0.11 285 / 0.5));
    border-radius: 3px;
  }
  
  .tipex-scrollbar-premium::-webkit-scrollbar-thumb:hover {
    background: linear-gradient(to bottom, 
      oklch(0.65 0.11 285 / 0.5), 
      oklch(0.55 0.11 285 / 0.7));
  }
  
  .dark .tipex-scrollbar-premium {
    scrollbar-color: oklch(0.65 0.11 285 / 0.4) transparent;
  }
  
  .dark .tipex-scrollbar-premium::-webkit-scrollbar-thumb {
    background: linear-gradient(to bottom, 
      oklch(0.65 0.11 285 / 0.3), 
      oklch(0.55 0.11 285 / 0.5));
  }
  
  .dark .tipex-scrollbar-premium::-webkit-scrollbar-thumb:hover {
    background: linear-gradient(to bottom, 
      oklch(0.65 0.11 285 / 0.5), 
      oklch(0.55 0.11 285 / 0.7));
  }
}

This example demonstrates Tailwind v4's powerful theming capabilities including gradient backgrounds, advanced shadows, custom scrollbars, and sophisticated component styling that would be much more complex in older Tailwind versions.

🚫 Why Tailwind v4 Only: Tipex exclusively supports and promotes Tailwind CSS v4 because it represents the future of utility-first CSS. The new architecture provides native CSS custom property integration, better performance, improved developer experience, and more maintainable code. We strongly discourage and do NOT support using older Tailwind versions (v1.x, v2.x, v3.x) as they lack the modern features that make Tipex's theming system possible.

⚠️ Legacy Tailwind Warning: Attempting to use Tipex with older Tailwind versions will result in broken styling, missing features, and poor performance. Upgrade to Tailwind v4 immediately.