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

  1. Provide Defaults: Start with a set of useful default colors

  2. Limit Count: Cap custom colors at 10-20 to avoid clutter

  3. Validate Input: Ensure colors are valid before adding

  4. Persist Data: Save colors to localStorage or backend

  5. Avoid Duplicates: Check for duplicate colors before adding

  6. Visual Feedback: Show color swatches clearly in the UI

  7. Accessibility: Ensure sufficient contrast for readability

Troubleshooting

Colors Not Appearing

  • Verify the userDefinedColors array is properly formatted

  • Check 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 Set to ensure uniqueness if needed

const addColor = (color: string) => {
  setUserDefinedColors((prev) => {
    if (prev.includes(color)) return prev;
    return [...prev, color];
  });
};

Last updated

Was this helpful?