Overview
DoctorSoft+ provides a comprehensive theming system that allows users to customize the application’s appearance including color schemes, typography, button styles, and font sizes.
Theme context
The theming system is built around ThemeContext which manages theme preferences and applies them globally.
Using the theme hook
import { useTheme } from '../contexts/ThemeContext' ;
function MyComponent () {
const {
currentTheme ,
setTheme ,
fontSize ,
setFontSize ,
buttonStyle ,
setButtonStyle
} = useTheme ();
return (
< div style = { { color: currentTheme . colors . text } } >
Hello, themed world!
</ div >
);
}
Available themes
DoctorSoft+ includes multiple built-in color themes:
The default theme with a clean, bright appearance suitable for well-lit environments.
A dark color scheme that reduces eye strain in low-light conditions.
A calming green-based theme.
A warm orange-based theme.
A professional blue-based theme.
Switching themes
Change the active theme using the setTheme function:
import { useTheme } from '../contexts/ThemeContext' ;
function ThemeSelector () {
const { setTheme } = useTheme ();
return (
< div >
< button onClick = { () => setTheme ( 'light' ) } > Light </ button >
< button onClick = { () => setTheme ( 'dark' ) } > Dark </ button >
< button onClick = { () => setTheme ( 'forest-green' ) } > Forest Green </ button >
< button onClick = { () => setTheme ( 'ocean-blue' ) } > Ocean Blue </ button >
< button onClick = { () => setTheme ( 'sunset-orange' ) } > Sunset Orange </ button >
</ div >
);
}
Theme preferences are automatically saved to localStorage and persist across sessions.
Appearance settings component
The AppearanceSettings component (src/components/AppearanceSettings.tsx) provides a complete UI for customizing the application appearance.
Theme selector
Display all available themes in a grid:
import { themes } from '../types/theme' ;
import { useTheme } from '../contexts/ThemeContext' ;
function ThemeGrid () {
const { currentTheme , setTheme } = useTheme ();
return (
< div className = "flex flex-wrap gap-3" >
{ Object . values ( themes ). map (( theme ) => (
< button
key = { theme . id }
onClick = { () => setTheme ( theme . id ) }
className = { currentTheme . id === theme . id ? 'selected' : '' }
style = { {
background: theme . colors . surface ,
color: theme . colors . text ,
borderColor: currentTheme . id === theme . id
? theme . colors . primary
: theme . colors . border ,
} }
>
< div
style = { { background: theme . colors . primary } }
className = "w-full h-12 rounded-md"
/>
< span > { theme . name } </ span >
</ button >
)) }
</ div >
);
}
Color system
Each theme provides a comprehensive color palette:
Core colors
const { currentTheme } = useTheme ();
// Primary brand color
currentTheme . colors . primary
// Background colors
currentTheme . colors . background // Main background
currentTheme . colors . surface // Card/panel background
// Text colors
currentTheme . colors . text // Primary text
currentTheme . colors . textSecondary // Secondary/muted text
// UI elements
currentTheme . colors . border // Borders and dividers
// Primary button
currentTheme . colors . buttonPrimary // Background
currentTheme . colors . buttonText // Text color
// Secondary button
currentTheme . colors . buttonSecondary
currentTheme . colors . buttonSecondaryText
Applying theme colors
< div
style = { {
background: currentTheme . colors . surface ,
color: currentTheme . colors . text ,
borderColor: currentTheme . colors . border ,
} }
>
Themed content
</ div >
Typography system
Font families
Each theme can specify different font families for various text elements:
const { currentTheme } = useTheme ();
// Apply fonts
style = {{
fontFamily : currentTheme . typography . fonts . headings , // For headings
fontFamily : currentTheme . typography . fonts . subheadings , // For subheadings
fontFamily : currentTheme . typography . fonts . body , // For body text
fontFamily : currentTheme . typography . fonts . ui , // For UI elements
}}
Font size customization
Users can adjust the global font size from 80% to 120%:
import { useTheme } from '../contexts/ThemeContext' ;
function FontSizeControl () {
const { fontSize , setFontSize } = useTheme ();
return (
< div >
< input
type = "range"
min = "80"
max = "120"
value = { fontSize }
onChange = { ( e ) => setFontSize ( Number ( e . target . value )) }
/>
< span > { fontSize } % </ span >
</ div >
);
}
Font size preferences are saved to localStorage and applied globally using CSS variables.
Preview font changes
Show users how their font size selection looks:
< div
className = "p-4 rounded-lg"
style = { {
background: currentTheme . colors . background ,
fontSize: ` ${ fontSize } %` ,
color: currentTheme . colors . text ,
} }
>
< p > Texto de ejemplo - Más vale una onza de salud que una libra de oro. </ p >
</ div >
DoctorSoft+ offers three button style options:
setButtonStyle ( 'rounded' ); // Soft rounded corners (0.5rem border-radius)
setButtonStyle ( 'square' ); // Sharp corners (no border-radius)
setButtonStyle ( 'pill' ); // Fully rounded (9999px border-radius)
import { useTheme } from '../contexts/ThemeContext' ;
import clsx from 'clsx' ;
function ThemedButton ({ children , onClick }) {
const { currentTheme , buttonStyle } = useTheme ();
const buttonClass = clsx (
'px-4 py-2 transition-colors' ,
buttonStyle === 'pill' && 'rounded-full' ,
buttonStyle === 'rounded' && 'rounded-lg' ,
buttonStyle === 'square' && 'rounded-none' ,
currentTheme . buttons ?. shadow && 'shadow-sm hover:shadow-md' ,
currentTheme . buttons ?. animation && 'hover:scale-105'
);
const buttonStyles = {
background: currentTheme . colors . buttonPrimary ,
color: currentTheme . colors . buttonText ,
};
return (
< button className = { buttonClass } style = { buttonStyles } onClick = { onClick } >
{ children }
</ button >
);
}
CSS variables
The theme system automatically sets CSS variables for easy styling:
:root {
/* Colors */
--color-primary : #your-primary-color;
--color-background : #your-background;
--color-text : #your-text-color;
/* ... and more */
/* Fonts */
--font-headings : "Inter" , sans-serif ;
--font-body : "Inter" , sans-serif ;
/* Font size */
--user-font-size-percentage : 100 ;
}
Use them in your CSS:
.my-element {
color : var ( --color-text );
background : var ( --color-surface );
font-family : var ( --font-headings );
}
Theme persistence
Theme preferences are automatically saved and loaded:
Initial load
Theme preferences are loaded synchronously on module load for instant theme application without flash.
User changes
When users change theme settings, they’re immediately saved to localStorage:
Theme ID: 'app-theme'
Font size: 'app-font-size'
Button style: 'app-button-style'
Next session
Preferences are automatically restored when the user returns.
The theme system is optimized for performance:
Memoized CSS variables
const cssVariables = useMemo (() => {
const vars : Record < string , string > = {};
// Generate CSS variables from theme
for ( const key in currentTheme . colors ) {
vars [ `--color- ${ key } ` ] = currentTheme . colors [ key ];
}
return vars ;
}, [ currentTheme , fontSize ]);
Efficient DOM updates
useEffect (() => {
const root = document . documentElement ;
requestAnimationFrame (() => {
for ( const [ property , value ] of Object . entries ( cssVariables )) {
root . style . setProperty ( property , value );
}
});
}, [ cssVariables ]);
CSS variable updates are batched using requestAnimationFrame to prevent layout thrashing.
Building the appearance settings UI
Create a complete appearance customization interface:
import { AppearanceSettings } from '../components/AppearanceSettings' ;
function SettingsPage () {
return (
< div >
< h1 > Configuración de Apariencia </ h1 >
< AppearanceSettings />
</ div >
);
}
The AppearanceSettings component includes:
Theme color selector with preview swatches
Button style selector with visual examples
Font size slider with live preview
Best practices
Always use theme colors
Instead of hardcoding colors, always use theme values: // Good
style = {{ color : currentTheme . colors . text }}
// Bad
style = {{ color : '#333333' }}
Support all button styles
Ensure your buttons work with all three style variants (rounded, square, pill).
Test with different font sizes
Make sure your UI doesn’t break when users select 80% or 120% font size.
Use semantic color names
Use currentTheme.colors.text instead of currentTheme.colors.primary for text to ensure proper contrast.
Custom theme creation
To add a new theme, define it in the themes object:
export const themes = {
// Existing themes...
'custom-theme' : {
id: 'custom-theme' ,
name: 'Custom Theme' ,
colors: {
primary: '#your-color' ,
background: '#your-bg' ,
surface: '#your-surface' ,
text: '#your-text' ,
textSecondary: '#your-secondary' ,
border: '#your-border' ,
buttonPrimary: '#your-button' ,
buttonText: '#your-button-text' ,
buttonSecondary: '#your-secondary-btn' ,
buttonSecondaryText: '#your-secondary-text' ,
},
typography: {
fonts: {
headings: 'Inter' ,
subheadings: 'Inter' ,
body: 'Inter' ,
ui: 'Inter' ,
},
},
buttons: {
style: 'rounded' ,
shadow: true ,
animation: true ,
},
},
};
Ensure sufficient color contrast between text and background colors for accessibility compliance.