Yoopta Editor provides a theme system that allows you to apply consistent visual styling across plugins. Themes are implemented as UI component overlays that can be applied to existing plugins.
What is a Theme?
A theme is a collection of styled React components that replace the default rendering of plugin elements. Instead of modifying plugin code, themes provide an elegant way to customize the visual appearance.
Available Themes
Yoopta Editor provides three built-in theme packages:
@yoopta/theme-base - Minimal base styling
@yoopta/theme-material - Material Design styled components
@yoopta/theme-shadcn - Shadcn UI styled components
Using a Theme
Installation
npm install @yoopta/theme-shadcn
# or
yarn add @yoopta/theme-shadcn
Applying a Theme
Use the applyTheme function to apply theme components to your plugins:
packages/themes/shadcn/src/applyTheme.ts
import { applyTheme } from '@yoopta/theme-shadcn' ;
import { Paragraph , HeadingOne , Accordion } from '@yoopta/plugins' ;
const themedPlugins = applyTheme ([
Paragraph ,
HeadingOne ,
Accordion ,
]);
const editor = createYooptaEditor ({
plugins: themedPlugins ,
marks: MARKS ,
});
The applyTheme function automatically maps theme components to their corresponding plugins. It returns new plugin instances with themed rendering.
How Themes Work
Themes use the plugin extend method to replace element render functions:
packages/themes/shadcn/src/applyTheme.ts
export function applyTheme (
plugins : PluginWithUI [],
extensions ?: PluginExtensions ,
) : PluginWithUI [] {
// Mapping of plugin types to their UI components
const uiMap : Record < string , any > = {
Accordion: AccordionUI ,
Paragraph: ParagraphUI ,
Blockquote: BlockquoteUI ,
// ... more mappings
};
return plugins . map (( plugin ) => {
const pluginType = plugin . getPlugin . type ;
const ui = uiMap [ pluginType ];
if ( ! ui ) return plugin ; // No theme for this plugin
// Apply theme UI
return plugin . extend ({
elements: ui ,
... extensions ?.[ pluginType ],
});
});
}
Theme Extensions
You can provide additional configuration when applying a theme:
import { applyTheme } from '@yoopta/theme-shadcn' ;
import { Accordion , Paragraph , HeadingOne } from '@yoopta/plugins' ;
const themedPlugins = applyTheme (
[ Accordion , Paragraph , HeadingOne ],
{
Accordion: {
// Allow nested plugins in accordion content
injectElementsFromPlugins: [ Paragraph , HeadingOne ],
// Custom element overrides
elements: {
'accordion-list-item' : {
props: {
defaultExpanded: true
},
},
},
},
}
);
Extension Options
type PluginExtensions = Record < string , {
injectElementsFromPlugins ?: PluginWithUI [];
events ?: any ;
options ?: any ;
elements ?: any ;
}>;
Option Description injectElementsFromPluginsAllow elements from other plugins to be nested eventsOverride or add event handlers optionsOverride plugin options elementsOverride specific element configurations
Creating Custom Themes
You can create your own theme package:
1. Define Styled Components
// my-theme/paragraph.tsx
import type { PluginElementRenderProps } from '@yoopta/editor' ;
export const ParagraphUI = {
paragraph: {
render : ( props : PluginElementRenderProps ) => (
< p
className = "my-theme-paragraph"
{ ... props . attributes }
>
{ props . children }
</ p >
),
},
};
2. Create Theme Mappings
// my-theme/index.ts
import { ParagraphUI } from './paragraph' ;
import { HeadingOneUI , HeadingTwoUI } from './headings' ;
import { CalloutUI } from './callout' ;
export const MyThemeUI = {
Paragraph: ParagraphUI ,
HeadingOne: HeadingOneUI ,
HeadingTwo: HeadingTwoUI ,
Callout: CalloutUI ,
// ... more components
};
3. Create Apply Theme Function
// my-theme/applyTheme.ts
import type { YooptaPlugin } from '@yoopta/editor' ;
import { MyThemeUI } from './index' ;
type PluginWithUI = YooptaPlugin < any , any >;
export function applyMyTheme (
plugins : PluginWithUI [],
extensions ?: Record < string , any >,
) : PluginWithUI [] {
return plugins . map (( plugin ) => {
const pluginType = plugin . getPlugin . type ;
const ui = MyThemeUI [ pluginType ];
if ( ! ui ) return plugin ;
const extension = extensions ?.[ pluginType ];
const elementsToApply = extension ?. elements ?? ui ;
return plugin . extend ({
elements: elementsToApply ,
... ( extension && {
injectElementsFromPlugins: extension . injectElementsFromPlugins ,
events: extension . events ,
options: extension . options ,
}),
});
});
}
4. Use Your Theme
import { applyMyTheme } from './my-theme' ;
import { Paragraph , HeadingOne } from '@yoopta/plugins' ;
const themedPlugins = applyMyTheme ([ Paragraph , HeadingOne ]);
const editor = createYooptaEditor ({
plugins: themedPlugins ,
marks: MARKS ,
});
Theme Anatomy
A complete theme component includes all element types for a plugin:
// Accordion theme UI
export const AccordionUI = {
'accordion-list' : {
render : ( props ) => (
< div className = "accordion-list" { ... props . attributes } >
{ props . children }
</ div >
),
},
'accordion-list-item' : {
render : ( props ) => {
const { isExpanded } = props . element . props || {};
return (
< details
className = "accordion-item"
open = { isExpanded }
{ ... props . attributes }
>
{ props . children }
</ details >
);
},
},
'accordion-list-item-heading' : {
render : ( props ) => (
< summary className = "accordion-heading" { ... props . attributes } >
{ props . children }
</ summary >
),
},
'accordion-list-item-content' : {
render : ( props ) => (
< div className = "accordion-content" { ... props . attributes } >
{ props . children }
</ div >
),
},
};
Styling Considerations
CSS Classes
Use consistent class naming conventions:
// BEM-style naming
render : ( props ) => (
< div
className = "yoopta-accordion__item"
data - expanded = {props.element.props. isExpanded }
{ ... props . attributes }
>
{ props . children }
</ div >
)
CSS-in-JS
Use styled-components, emotion, or other CSS-in-JS libraries:
import styled from 'styled-components' ;
const StyledParagraph = styled . p `
font-size: 16px;
line-height: 1.5;
margin: 1rem 0;
color: ${ props => props . theme . text } ;
` ;
export const ParagraphUI = {
paragraph: {
render : ( props ) => (
< StyledParagraph { ... props . attributes } >
{ props . children }
</ StyledParagraph >
),
},
};
Tailwind CSS
Use Tailwind utility classes:
export const ParagraphUI = {
paragraph: {
render : ( props ) => (
< p
className = "text-base leading-relaxed my-4 text-gray-900 dark:text-gray-100"
{ ... props . attributes }
>
{ props . children }
</ p >
),
},
};
Theming Mark Components
While themes primarily focus on plugins, you can also style marks:
import { createYooptaMark } from '@yoopta/editor' ;
const ThemedBold = createYooptaMark ({
type: 'bold' ,
hotkey: 'mod+b' ,
render : ({ children }) => (
< strong className = "font-bold text-gray-900" >
{ children }
</ strong >
),
});
const ThemedItalic = createYooptaMark ({
type: 'italic' ,
hotkey: 'mod+i' ,
render : ({ children }) => (
< em className = "italic text-gray-800" >
{ children }
</ em >
),
});
const THEMED_MARKS = [ ThemedBold , ThemedItalic , /* ... */ ];
Partial Theme Application
You can apply themes selectively:
import { applyTheme } from '@yoopta/theme-shadcn' ;
import { Paragraph , HeadingOne , Code } from '@yoopta/plugins' ;
// Apply theme to some plugins
const themedPlugins = applyTheme ([ Paragraph , HeadingOne ]);
// Use default styling for others
const allPlugins = [ ... themedPlugins , Code ];
const editor = createYooptaEditor ({
plugins: allPlugins ,
marks: MARKS ,
});
Responsive Theming
Create responsive theme components:
export const HeadingOneUI = {
'heading-one' : {
render : ( props ) => (
< h1
className = "
text-3xl md:text-4xl lg:text-5x l
font - bold
my - 4 md : my - 6 lg : my - 8
"
{...props.attributes }
>
{ props . children }
</ h1 >
),
},
};
Dark Mode Support
Implement dark mode in your theme:
export const ParagraphUI = {
paragraph: {
render : ( props ) => (
< p
className = "
text-gray-900 dark:text-gray-10 0
bg - white dark : bg - gray - 800
"
{...props.attributes }
>
{ props . children }
</ p >
),
},
};
Global Theme Styles
Provide global CSS for the editor:
/* my-theme/styles.css */
.yoopta-editor {
font-family : 'Inter' , sans-serif ;
font-size : 16 px ;
line-height : 1.5 ;
color : #1a1a1a ;
}
.yoopta-editor h1 {
font-size : 2.5 rem ;
font-weight : 700 ;
margin : 1.5 rem 0 ;
}
.yoopta-editor p {
margin : 1 rem 0 ;
}
.yoopta-editor code {
background : #f5f5f5 ;
padding : 0.2 rem 0.4 rem ;
border-radius : 0.25 rem ;
font-family : 'Fira Code' , monospace ;
}
Theme Best Practices
1. Maintain Consistency
Ensure all components follow the same design system:
// Define consistent spacing
const SPACING = {
xs: '0.25rem' ,
sm: '0.5rem' ,
md: '1rem' ,
lg: '1.5rem' ,
xl: '2rem' ,
};
// Use across components
export const HeadingUI = {
render : ( props ) => (
< h1 style = {{ margin : ` ${ SPACING . lg } 0` }} { ... props . attributes } >
{ props . children }
</ h1 >
),
};
2. Preserve Accessibility
Ensure themed components remain accessible:
render : ( props ) => (
< button
className = "theme-button"
aria - label = "Toggle accordion"
{ ... props . attributes }
>
{ props . children }
</ button >
)
3. Keep Semantic HTML
Don’t sacrifice semantics for styling:
// Good: Semantic HTML
render : ( props ) => < h1 { ... props . attributes }>{props. children } </ h1 >
// Bad: Div with heading styles
render : ( props ) => < div className = "h1-styled" { ... props . attributes }>{props. children } </ div >
4. Document Theme Usage
Provide clear documentation for your theme:
/**
* My Custom Theme
*
* A modern theme with:
* - Clean typography
* - Responsive spacing
* - Dark mode support
*
* @example
* ```typescript
* import { applyMyTheme } from '@my-org/yoopta-theme';
*
* const plugins = applyMyTheme([Paragraph, HeadingOne]);
* ```
*/
export function applyMyTheme ( plugins : PluginWithUI []) : PluginWithUI [] {
// Implementation
}
5. Test Across Plugins
Ensure your theme works well with all supported plugins:
// Test with all plugins
const ALL_PLUGINS = [
Paragraph ,
HeadingOne ,
HeadingTwo ,
HeadingThree ,
Blockquote ,
Code ,
Image ,
// ... etc
];
const themedPlugins = applyMyTheme ( ALL_PLUGINS );
Next Steps
Plugins Learn about the plugin system
Elements Understand element structures
Marks Style text formatting
Shadcn Theme Explore the Shadcn theme