User-Defined Colors
Create custom color palettes for your spreadsheet
User-defined colors allow you to extend the default color palette with custom colors that persist across your application. Users can add their own colors to color pickers for text, backgrounds, and borders.
Overview
By default, color selectors in the toolbar (background color, text color, border color) provide a standard palette. User-defined colors extend this palette with:
Custom brand colors: Add your organization's brand colors
Project-specific palettes: Create color schemes for different projects
User preferences: Allow users to save frequently used colors
Persistent colors: Colors that persist across sessions
Basic Usage
import React, { useState } from "react";
import {
SpreadsheetProvider,
CanvasGrid,
Toolbar,
BackgroundColorSelector,
TextColorSelector,
BorderSelector,
} from "@rowsncolumns/spreadsheet";
function SpreadsheetWithCustomColors() {
const [userDefinedColors, setUserDefinedColors] = useState<string[]>([
"#FF6B6B", // Coral Red
"#4ECDC4", // Turquoise
"#45B7D1", // Sky Blue
"#FFA07A", // Light Salmon
]);
const handleAddColor = (color: string) => {
setUserDefinedColors((prev) => [...prev, color]);
};
return (
<SpreadsheetProvider>
<Toolbar>
<BackgroundColorSelector
color={currentCellFormat?.backgroundColor}
theme={theme}
userDefinedColors={userDefinedColors}
onAddUserDefinedColor={handleAddColor}
onChange={onChangeBackgroundColor}
/>
<TextColorSelector
color={currentCellFormat?.textFormat?.color}
theme={theme}
isDarkMode={isDarkMode}
userDefinedColors={userDefinedColors}
onAddUserDefinedColor={handleAddColor}
onChange={onChangeTextColor}
/>
<BorderSelector
borders={currentCellFormat?.borders}
theme={theme}
isDarkMode={isDarkMode}
userDefinedColors={userDefinedColors}
onAddUserDefinedColor={handleAddColor}
onChange={onChangeBorder}
/>
</Toolbar>
<CanvasGrid
// ... props
/>
</SpreadsheetProvider>
);
}State Management
Simple State
Use React state for basic color management:
function MySpreadsheet() {
const [userDefinedColors, setUserDefinedColors] = useState<string[]>([]);
const addColor = (color: string) => {
setUserDefinedColors((prev) => [...prev, color]);
};
return (
<BackgroundColorSelector
userDefinedColors={userDefinedColors}
onAddUserDefinedColor={addColor}
// ... other props
/>
);
}Persistent Storage
Save colors to localStorage for persistence:
function MySpreadsheet() {
const [userDefinedColors, setUserDefinedColors] = useState<string[]>(() => {
// Load from localStorage on mount
const saved = localStorage.getItem("spreadsheet-custom-colors");
return saved ? JSON.parse(saved) : [];
});
const addColor = (color: string) => {
setUserDefinedColors((prev) => {
const updated = [...prev, color];
// Save to localStorage
localStorage.setItem(
"spreadsheet-custom-colors",
JSON.stringify(updated)
);
return updated;
});
};
return (
<BackgroundColorSelector
userDefinedColors={userDefinedColors}
onAddUserDefinedColor={addColor}
// ... other props
/>
);
}Server-Side Persistence
Save colors to your backend:
function MySpreadsheet() {
const [userDefinedColors, setUserDefinedColors] = useState<string[]>([]);
// Load colors from server
useEffect(() => {
async function loadColors() {
const response = await fetch("/api/user/colors");
const colors = await response.json();
setUserDefinedColors(colors);
}
loadColors();
}, []);
const addColor = async (color: string) => {
// Optimistically update UI
setUserDefinedColors((prev) => [...prev, color]);
// Save to server
try {
await fetch("/api/user/colors", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ color }),
});
} catch (error) {
console.error("Failed to save color:", error);
// Revert on error
setUserDefinedColors((prev) => prev.filter((c) => c !== color));
}
};
return (
<BackgroundColorSelector
userDefinedColors={userDefinedColors}
onAddUserDefinedColor={addColor}
// ... other props
/>
);
}Color Format
User-defined colors support various formats:
const [userDefinedColors, setUserDefinedColors] = useState([
"#FF6B6B", // Hex
"rgb(78, 205, 196)", // RGB
"rgba(78, 205, 196, 0.8)", // RGBA
"hsl(180, 60%, 55%)", // HSL
]);Complete Example
import React, { useState, useEffect } from "react";
import {
SpreadsheetProvider,
CanvasGrid,
Toolbar,
BackgroundColorSelector,
TextColorSelector,
BorderSelector,
Sheet,
SpreadsheetTheme,
defaultSpreadsheetTheme,
} from "@rowsncolumns/spreadsheet";
import {
useSpreadsheetState,
SheetData,
CellData,
} from "@rowsncolumns/spreadsheet-state";
// Default custom colors
const DEFAULT_CUSTOM_COLORS = [
"#FF6B6B", // Coral Red
"#4ECDC4", // Turquoise
"#45B7D1", // Sky Blue
"#FFA07A", // Light Salmon
"#96CEB4", // Sage Green
"#FFEAA7", // Warm Yellow
"#DFE6E9", // Light Gray
"#74B9FF", // Soft Blue
];
function SpreadsheetWithColors() {
const [sheets, setSheets] = useState<Sheet[]>([
{ sheetId: 1, rowCount: 100, columnCount: 26, title: "Sheet 1" }
]);
const [sheetData, setSheetData] = useState<SheetData<CellData>>({});
const [theme] = useState<SpreadsheetTheme>(defaultSpreadsheetTheme);
// Load user-defined colors from localStorage
const [userDefinedColors, setUserDefinedColors] = useState<string[]>(() => {
const saved = localStorage.getItem("user-colors");
return saved ? JSON.parse(saved) : DEFAULT_CUSTOM_COLORS;
});
const {
activeCell,
activeSheetId,
selections,
getCellData,
getEffectiveFormat,
onChangeActiveCell,
onChangeSelections,
onChange,
onChangeFormatting,
onChangeBorder,
isDarkMode,
} = useSpreadsheetState({
sheets,
sheetData,
onChangeSheets: setSheets,
onChangeSheetData: setSheetData,
});
// Get current cell format
const currentCellFormat = getEffectiveFormat(
activeSheetId,
activeCell.rowIndex,
activeCell.columnIndex
);
// Handle adding new color
const handleAddColor = (color: string) => {
setUserDefinedColors((prev) => {
// Avoid duplicates
if (prev.includes(color)) return prev;
const updated = [...prev, color];
// Save to localStorage
localStorage.setItem("user-colors", JSON.stringify(updated));
return updated;
});
};
// Handle background color change
const handleBackgroundColorChange = (color: any) => {
onChangeFormatting(
activeSheetId,
activeCell,
selections,
"backgroundColor",
color
);
};
// Handle text color change
const handleTextColorChange = (color: any) => {
onChangeFormatting(
activeSheetId,
activeCell,
selections,
"textFormat",
{ color }
);
};
return (
<SpreadsheetProvider>
<div className="flex flex-col h-screen">
<Toolbar>
<BackgroundColorSelector
color={currentCellFormat?.backgroundColor}
theme={theme}
userDefinedColors={userDefinedColors}
onAddUserDefinedColor={handleAddColor}
onChange={handleBackgroundColorChange}
/>
<TextColorSelector
color={currentCellFormat?.textFormat?.color}
theme={theme}
isDarkMode={isDarkMode}
userDefinedColors={userDefinedColors}
onAddUserDefinedColor={handleAddColor}
onChange={handleTextColorChange}
/>
<BorderSelector
borders={currentCellFormat?.borders}
theme={theme}
isDarkMode={isDarkMode}
userDefinedColors={userDefinedColors}
onAddUserDefinedColor={handleAddColor}
onChange={onChangeBorder}
/>
</Toolbar>
<div className="flex-1">
<CanvasGrid
sheetId={activeSheetId}
activeCell={activeCell}
selections={selections}
getCellData={getCellData}
onChangeActiveCell={onChangeActiveCell}
onChangeSelections={onChangeSelections}
onChange={onChange}
theme={theme}
/>
</div>
</div>
</SpreadsheetProvider>
);
}
export default SpreadsheetWithColors;Advanced Features
Color Validation
Validate colors before adding them:
function isValidColor(color: string): boolean {
const style = new Option().style;
style.color = color;
return style.color !== "";
}
const addColor = (color: string) => {
if (!isValidColor(color)) {
console.error("Invalid color:", color);
return;
}
setUserDefinedColors((prev) => [...prev, color]);
};Color Limits
Limit the number of custom colors:
const MAX_CUSTOM_COLORS = 20;
const addColor = (color: string) => {
setUserDefinedColors((prev) => {
if (prev.length >= MAX_CUSTOM_COLORS) {
console.warn("Maximum custom colors reached");
return prev;
}
return [...prev, color];
});
};Color Organization
Organize colors by category:
const [colorCategories, setColorCategories] = useState({
brand: ["#FF6B6B", "#4ECDC4"],
pastels: ["#FFA07A", "#96CEB4"],
vibrant: ["#45B7D1", "#FFEAA7"],
});
// Flatten for color selectors
const allColors = Object.values(colorCategories).flat();Remove Colors
Allow users to remove custom colors:
function ColorManager() {
const [userDefinedColors, setUserDefinedColors] = useState<string[]>([]);
const removeColor = (colorToRemove: string) => {
setUserDefinedColors((prev) =>
prev.filter((color) => color !== colorToRemove)
);
};
return (
<div className="space-y-2">
{userDefinedColors.map((color) => (
<div key={color} className="flex items-center gap-2">
<div
className="w-8 h-8 rounded border"
style={{ backgroundColor: color }}
/>
<span className="flex-1">{color}</span>
<button
onClick={() => removeColor(color)}
className="px-2 py-1 text-sm text-red-600"
>
Remove
</button>
</div>
))}
</div>
);
}Integration with Theme Colors
User-defined colors work alongside theme colors:
import { SpreadsheetTheme } from "@rowsncolumns/spreadsheet";
const customTheme: SpreadsheetTheme = {
...defaultSpreadsheetTheme,
colors: [
"#000000", // Black
"#FFFFFF", // White
"#FF0000", // Red
// ... theme colors
],
};
// User-defined colors appear separately from theme colors
const [userDefinedColors, setUserDefinedColors] = useState([
"#FF6B6B",
"#4ECDC4",
]);Use Cases
Brand Colors
const brandColors = [
"#1E40AF", // Primary Blue
"#F59E0B", // Accent Orange
"#10B981", // Success Green
"#EF4444", // Error Red
];
const [userDefinedColors, setUserDefinedColors] = useState(brandColors);Project-Specific Palettes
const projectPalettes = {
marketing: ["#FF6B6B", "#4ECDC4", "#FFA07A"],
finance: ["#1E40AF", "#10B981", "#F59E0B"],
design: ["#8B5CF6", "#EC4899", "#F59E0B"],
};
const [activeProject, setActiveProject] = useState("marketing");
const userDefinedColors = projectPalettes[activeProject];User Preferences
// Load user's saved colors from profile
const { user } = useAuth();
const [userDefinedColors, setUserDefinedColors] = useState(
user.preferences.customColors || []
);Best Practices
Provide Defaults: Start with a set of useful default colors
Limit Count: Cap custom colors at 10-20 to avoid clutter
Validate Input: Ensure colors are valid before adding
Persist Data: Save colors to localStorage or backend
Avoid Duplicates: Check for duplicate colors before adding
Visual Feedback: Show color swatches clearly in the UI
Accessibility: Ensure sufficient contrast for readability
Troubleshooting
Colors Not Appearing
Verify the
userDefinedColorsarray is properly formattedCheck that color values are valid CSS colors
Ensure the array is passed to all color selector components
Colors Not Persisting
Confirm localStorage save logic is working
Check for browser storage limits
Verify localStorage keys are consistent
Duplicate Colors
Implement duplicate checking before adding colors
Use
Setto ensure uniqueness if needed
const addColor = (color: string) => {
setUserDefinedColors((prev) => {
if (prev.includes(color)) return prev;
return [...prev, color];
});
};Last updated
Was this helpful?