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.
Tipex offers multiple layers of 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 */
@import "tailwindcss";
@theme {
/* Tipex Design System Override */
--color-tipex-*: initial;
/* Custom Primary Colors */
--color-tipex-primary: #10b981; /* Custom green */
--color-tipex-primary-dark: #34d399;
/* Editor Colors */
--color-tipex-editor-bg: #fafafa;
--color-tipex-editor-bg-dark: #0f172a;
--color-tipex-editor-text: #111827;
--color-tipex-editor-text-dark: #f9fafb;
/* Control Colors */
--color-tipex-control-bg: #ffffff;
--color-tipex-control-bg-dark: #1f2937;
--color-tipex-control-hover: #f1f5f9;
--color-tipex-control-hover-dark: #374151;
--color-tipex-control-active: #dcfce7;
--color-tipex-control-active-dark: #1e3a8a;
/* Typography Scale */
--text-tipex-base: 1.125rem;
--text-tipex-lg: 1.25rem;
--text-tipex-xl: 1.5rem;
/* Spacing Scale */
--spacing-tipex-xs: 0.25rem;
--spacing-tipex-sm: 0.5rem;
--spacing-tipex-md: 1rem;
--spacing-tipex-lg: 1.5rem;
--spacing-tipex-xl: 2rem;
/* Border Radius */
--radius-tipex-sm: 0.25rem;
--radius-tipex-md: 0.375rem;
--radius-tipex-lg: 0.5rem;
/* Shadows */
--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(16 185 129 / 0.5);
--shadow-tipex-focus-dark: 0 0 0 3px rgb(52 211 153 / 0.5);
}
/* Custom Dark Mode Variant (Tailwind v4) */
@custom-variant dark (&:where(.dark, .dark *));
/* Component Layer Customization */
@layer components {
.tipex-editor {
@apply bg-tipex-editor-bg dark:bg-tipex-editor-bg-dark
border-tipex-editor-border dark:border-tipex-editor-border-dark
rounded-tipex-md;
}
.tipex-editor.focused.focal {
@apply shadow-tipex-focus dark:shadow-tipex-focus-dark
border-transparent outline-none;
}
.custom-editor-theme {
@apply bg-gradient-to-br from-tipex-editor-bg to-tipex-control-bg
dark:from-tipex-editor-bg-dark dark:to-tipex-control-bg-dark;
}
}
The built-in utility section includes essential tools like copy functionality and link management. You can easily integrate these or add your own custom utilities.
Include the default utility buttons (copy, link management) in your editor:
import {Utility} from "@friendofsvelte/tipex";
<Tipex body={body}>
{#snippet utilities(tipex)}
<Utility {tipex}/>
{/snippet}
</Tipex>
Extend the utility area with your own custom buttons and functionality:
<Tipex body={body}>
{#snippet utilities(tipex)}
<div aria-label="Custom utility button">...</div>
{/snippet}
</Tipex>
Here's a more comprehensive example with multiple custom utilities:
<Tipex body={body}>
{#snippet utilities(tipex)}
<!-- Built-in utilities -->
<Utility {tipex}/>
<!-- Custom utilities -->
<button
class="tipex-edit-button tipex-button-rigid"
onclick={() => exportToPDF(tipex.getHTML())}
aria-label="Export to PDF"
>
<iconify-icon icon="fa6-solid:file-pdf"></iconify-icon>
</button>
<button
class="tipex-edit-button tipex-button-rigid"
onclick={() => insertTemplate(tipex)}
aria-label="Insert template"
>
<iconify-icon icon="fa6-solid:file-contract"></iconify-icon>
</button>
<button
class="tipex-edit-button tipex-button-rigid"
onclick={() => toggleFullscreen()}
aria-label="Toggle fullscreen"
>
<iconify-icon icon="fa6-solid:expand"></iconify-icon>
</button>
{/snippet}
</Tipex>
Prop Name | Default | Condition | Description |
---|---|---|---|
head(tipex) | undefined | Optional | A slot that accepts a function receiving the TipexEditor instance, rendered above the main editor content area |
controlComponent | Snippet<[TipexEditor]> | Only used when you want to replace default controls entirely | |
utilities | Snippet<[TipexEditor]> | Only used when you want to customize the default controls toolbar | |
foot(tipex) | undefined | Optional | A slot that accepts a function receiving the TipexEditor instance, rendered below the editor content and controls |
Tipex automatically detects which control system to use:
controlComponent
is provided: Uses your custom control componentcontrolComponent
is not provided: Shows default controls with optional utilities
This ensures a clean API where you don't need to manage boolean flags.
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-tipex-md p-tipex-lg
bg-tipex-control-bg dark:bg-tipex-control-bg-dark
rounded-tipex-md shadow-tipex-sm
border border-tipex-control-border dark:border-tipex-control-border-dark;
}
</style>
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.
@theme
configuration@custom-variant
syntax for advanced dark modeTipex exclusively uses Tailwind CSS v4 for superior developer experience and performance:
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.
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>
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 utilities(tipex)}
<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"
>
<iconify-icon icon="fa6-solid:image"></iconify-icon>
</button>
{/snippet}
</Tipex>
Leverage TipTap's powerful extension system to add custom functionality. Tipex provides easy access to modify and extend the editor's capabilities:
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} />
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"
>
<iconify-icon icon="fa6-solid:highlighter"></iconify-icon>
</button>
<!-- Word count display -->
<span class="word-count">
Words: {tipex.storage.wordCount?.wordCount || 0}
</span>
</div>
{/snippet}
</Tipex>
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>
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>
Optimize your customized editor using Tailwind v4's 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.
Create sophisticated themes using Tailwind v4's advanced features. Here's a complete example of a premium theme configuration:
/* Custom Tipex Theme with Tailwind v4 */
@import "tailwindcss";
@theme {
/* Custom Tipex Design System */
--color-tipex-*: initial;
/* Brand Colors */
--color-tipex-primary: #8b5cf6; /* Purple brand */
--color-tipex-primary-dark: #a78bfa;
--color-tipex-secondary: #64748b;
--color-tipex-secondary-dark: #94a3b8;
/* Custom Editor Theme */
--color-tipex-editor-bg: #fefefe;
--color-tipex-editor-bg-dark: #0c0a09;
--color-tipex-editor-border: #e2e8f0;
--color-tipex-editor-border-dark: #292524;
--color-tipex-editor-text: #0f172a;
--color-tipex-editor-text-dark: #fafaf9;
/* Premium Control Colors */
--color-tipex-control-bg: #ffffff;
--color-tipex-control-bg-dark: #1c1917;
--color-tipex-control-hover: #f8fafc;
--color-tipex-control-hover-dark: #292524;
--color-tipex-control-active: #ede9fe;
--color-tipex-control-active-dark: #581c87;
--color-tipex-control-border: #cbd5e1;
--color-tipex-control-border-dark: #44403c;
/* 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;
/* Custom Spacing */
--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;
/* Modern Shadows */
--shadow-tipex-sm: 0 1px 3px 0 rgb(0 0 0 / 0.08);
--shadow-tipex-md: 0 4px 8px -2px rgb(0 0 0 / 0.12);
--shadow-tipex-lg: 0 8px 16px -4px rgb(0 0 0 / 0.16);
--shadow-tipex-focus: 0 0 0 3px rgb(139 92 246 / 0.4);
--shadow-tipex-focus-dark: 0 0 0 3px rgb(167 139 250 / 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-editor-bg via-white to-tipex-control-bg
dark:from-tipex-editor-bg-dark dark:via-stone-900 dark:to-tipex-control-bg-dark
border-2 border-tipex-editor-border dark:border-tipex-editor-border-dark
rounded-tipex-lg shadow-tipex-md;
}
.tipex-editor-premium.focused.focal {
@apply shadow-tipex-focus dark:shadow-tipex-focus-dark
border-tipex-primary dark:border-tipex-primary-dark
outline-none ring-2 ring-tipex-primary/20 dark:ring-tipex-primary-dark/20;
}
.tipex-control-premium {
@apply bg-tipex-control-bg/90 dark:bg-tipex-control-bg-dark/90
backdrop-blur-sm border border-tipex-control-border/50 dark:border-tipex-control-border-dark/50
rounded-tipex-md shadow-tipex-sm;
}
.tipex-button-premium {
@apply px-tipex-md py-tipex-sm
bg-gradient-to-b from-tipex-control-bg to-tipex-control-hover
dark:from-tipex-control-bg-dark dark:to-tipex-control-hover-dark
border border-tipex-control-border dark:border-tipex-control-border-dark
rounded-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/40;
}
.tipex-button-premium.active {
@apply bg-gradient-to-b from-tipex-control-active to-tipex-primary/10
dark:from-tipex-control-active-dark dark:to-tipex-primary-dark/10
border-tipex-primary dark:border-tipex-primary-dark
text-tipex-primary dark:text-tipex-primary-dark
shadow-tipex-md ring-2 ring-tipex-primary/20 dark:ring-tipex-primary-dark/20;
}
.tipex-scrollbar-premium {
scrollbar-width: thin;
scrollbar-color: rgb(139 92 246 / 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, rgb(139 92 246 / 0.3), rgb(139 92 246 / 0.5));
border-radius: 3px;
}
.tipex-scrollbar-premium::-webkit-scrollbar-thumb:hover {
background: linear-gradient(to bottom, rgb(139 92 246 / 0.5), rgb(139 92 246 / 0.7));
}
.dark .tipex-scrollbar-premium {
scrollbar-color: rgb(167 139 250 / 0.4) transparent;
}
.dark .tipex-scrollbar-premium::-webkit-scrollbar-thumb {
background: linear-gradient(to bottom, rgb(167 139 250 / 0.3), rgb(167 139 250 / 0.5));
}
.dark .tipex-scrollbar-premium::-webkit-scrollbar-thumb:hover {
background: linear-gradient(to bottom, rgb(167 139 250 / 0.5), rgb(167 139 250 / 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.