React Data GridColor Picker Cell - Step-by-Step Guide

Overview

This guide shows how to create a custom color picker cell in Angular using the native HTML5 color input. Users can click a cell to open a color picker, select a color, and see it rendered with a colored circle swatch. No external libraries are required.

Difficulty: Beginner Time: ~15 minutes Libraries: None (uses native HTML5 <input type="color">)

Complete Example

What You'll Build

A cell that:

  • Displays a colored circle swatch in the cell
  • Opens a native HTML5 color picker when the cell is edited
  • Validates hex color format
  • Saves the value when a color is selected

Prerequisites

No external libraries required. This example uses:

  • @handsontable/angular-wrapper
  • Native HTML5 <input type="color">

Step 1: Import Dependencies

import { Component, ChangeDetectionStrategy } from '@angular/core';
import {
  GridSettings,
  HotCellEditorAdvancedComponent,
  HotCellRendererAdvancedComponent,
} from '@handsontable/angular-wrapper';
  • Handsontable's modules are registered in the Angular module (see Step 5) via registerAllModules().

Step 2: Create the Renderer Component

The renderer component controls how the cell looks when not being edited. It displays a colored circle swatch.

@Component({
  selector: 'example1-color-renderer',
  changeDetection: ChangeDetectionStrategy.OnPush,
  template: `
    <div class="color-picker-cell">
      <span class="color-picker-swatch" [style.background]="value"></span>
    </div>`,
  styles: `
  :host {
    height: 100%;
    width: 100%;
  }
  .color-picker-cell {
    display: flex;
    align-items: center;
    justify-content: center;
  }
  .color-picker-swatch {
    width: 18px;
    height: 18px;
    border-radius: 50%;
    flex-shrink: 0;
    border: 1px solid rgba(0, 0, 0, 0.15);
  }
  .color-picker-editor {
    width: 100%;
    height: 100%;
    box-sizing: border-box !important;
    cursor: pointer;
    border: none;
    outline: none;
  }
  `,
  standalone: false,
})
export class ColorRendererComponent extends HotCellRendererAdvancedComponent<string> {}
  • The template renders a circle swatch with the cell's color via [style.background]="value".
  • .color-picker-cell and .color-picker-swatch center and style the swatch; .color-picker-editor styles the editor input.
  • ChangeDetectionStrategy.OnPush is used to optimize performance.

Step 3: Create the Editor Component

The editor component uses the native HTML5 color input. When the user selects a color, the value is updated and afterClose calls finishEdit.emit() to save.

@Component({
  selector: 'example1-color-picker-editor',
  template: `
    <input
      class="color-picker-editor"
      type="color"
      [value]="value"
      (input)="onColorChange($event)"
    />
  `,
  styleUrls: ['./example1.css'],
  standalone: false,
})
export class ColorPickerEditorComponent extends HotCellEditorAdvancedComponent<string> {
  override afterClose(): void {
    this.finishEdit.emit();
  }

  onColorChange(event: Event): void {
    const input = event.target as HTMLInputElement;
    this.setValue(input.value);
  }
}

What's happening:

  • Extends HotCellEditorAdvancedComponent<string> - provides editor lifecycle
  • <input type="color"> is the native HTML5 color picker; no external library is required
  • [value]="value" binds the current cell value; (input)="onColorChange($event)" updates the value on change
  • afterClose() calls finishEdit.emit() so the value is saved when the editor closes

Lifecycle flow:

  1. User opens editor (double-click or F2)
  2. Input shows the current color
  3. User selects a color → onColorChange() calls setValue(input.value)
  4. User closes the editor (e.g. click outside) → afterClose() runs → finishEdit.emit() saves the value

Step 4: Add Validator

The validator ensures only valid hex colors are saved to the cell.

const colorValidator = (value: string): boolean => {
  return /^#[0-9A-Fa-f]{6}$/.test(value);
};

What's happening:

  • Simple function returning boolean - this is Angular's CustomValidatorFn<string> type
  • Uses regex to validate hex color format: # followed by 6 hex characters
  • Returns true for valid colors like "#FF0000", "#00ff00"
  • Returns false for invalid formats

Why add validation:

  • Ensures data consistency
  • Native color picker already outputs valid hex, but validation adds extra safety

Alternative validators:

// Support short format (#fff)
const flexibleValidator = (value: string): boolean =>
  /^#([0-9A-Fa-f]{3}|[0-9A-Fa-f]{6})$/.test(value);

Step 5: Register Components in Module

Register the custom components in your Angular module.

import { NgModule, ApplicationConfig } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { registerAllModules } from 'handsontable/registry';
import { HOT_GLOBAL_CONFIG, HotGlobalConfig, HotTableModule } from '@handsontable/angular-wrapper';
import { CommonModule } from '@angular/common';
import { NON_COMMERCIAL_LICENSE } from '@handsontable/angular-wrapper';
import {
  Example1GuideColorPickerAngularComponent,
  ColorPickerEditorComponent,
  ColorRendererComponent,
} from './app.component';

// Register Handsontable's modules
registerAllModules();

export const appConfig: ApplicationConfig = {
  providers: [
    {
      provide: HOT_GLOBAL_CONFIG,
      useValue: {
        license: NON_COMMERCIAL_LICENSE,
      } as HotGlobalConfig,
    },
  ],
};

@NgModule({
  imports: [BrowserModule, HotTableModule, CommonModule],
  declarations: [Example1GuideColorPickerAngularComponent, ColorPickerEditorComponent, ColorRendererComponent],
  providers: [...appConfig.providers],
  bootstrap: [Example1GuideColorPickerAngularComponent],
})
export class AppModule {}

What's happening:

  • Import HotTableModule for Handsontable Angular integration
  • Declare custom components and the root example component in declarations
  • Call registerAllModules() to enable all Handsontable features
  • Configure global Handsontable settings via HOT_GLOBAL_CONFIG
  • Set license (e.g. NON_COMMERCIAL_LICENSE)

Key points:

  • Custom editor and renderer must be declared in the same module
  • HotTableModule provides the <hot-table> component
  • Global config applies to all Handsontable instances in the app

Step 6: Configure Handsontable

Use the custom components in your Handsontable column configuration. The example adds a color property to each row (e.g. from inputData) and passes it to the grid.

@Component({
  selector: 'example1-guide-color-picker-angular',
  standalone: false,
  template: ` <div>
    <hot-table [data]="data" [settings]="gridSettings"></hot-table>
  </div>`,
})
export class Example1GuideColorPickerAngularComponent {
  readonly data = inputData.map((el) => ({
    ...el,
    color: `#${
      Math.round(0x1000000 + 0xffffff * Math.random())
        .toString(16)
        .slice(1)
        .toUpperCase()
    }`,
  }));

  readonly gridSettings: GridSettings = {
    autoRowSize: true,
    rowHeaders: true,
    autoWrapRow: true,
    height: 'auto',
    width: '100%',
    manualColumnResize: true,
    manualRowResize: true,
    colHeaders: ['ID', 'Item Name', 'Item Color', 'Item No.', 'Cost', 'Value in Stock'],
    columns: [
      {
        data: 'id',
        type: 'numeric',
        width: 80,
        headerClassName: 'htLeft',
      },
      {
        data: 'itemName',
        type: 'text',
        width: 200,
        headerClassName: 'htLeft',
      },
      {
        data: 'color',
        headerClassName: 'htLeft',
        editor: ColorPickerEditorComponent,
        renderer: ColorRendererComponent,
        validator: colorValidator,
      },
      {
        data: 'itemNo',
        type: 'text',
        width: 100,
        headerClassName: 'htLeft',
      },
      {
        data: 'cost',
        type: 'numeric',
        width: 70,
        headerClassName: 'htLeft',
      },
      {
        data: 'valueStock',
        type: 'numeric',
        width: 130,
        headerClassName: 'htRight',
      },
    ],
  };
}

What's happening:

  • [data]="data" - binds data array to Handsontable (with mapped color per row from inputData)
  • [settings]="gridSettings" - passes configuration object
  • editor: ColorPickerEditorComponent - uses custom editor class
  • renderer: ColorRendererComponent - uses custom renderer class (circle swatch)
  • validator: colorValidator - validates hex color format

Key configuration:

  • Pass component classes directly (not instances)
  • Angular wrapper handles component creation automatically
  • Column widths and headerClassName align with the table layout

Enhancements

1. Add Default Colors

Provide preset color options using a custom dropdown:

@Component({
  selector: "app-color-picker-editor-enhanced",
  template: `
    <div style="display: flex; flex-direction: column; height: 100%;">
      <input style="width: 100%; flex: 1;" type="color" [value]="value" (input)="onColorChange($event)" />
      <div style="display: flex; gap: 2px; padding: 2px;">
        @for (preset of presetColors; track preset) {
        <button
          [style.background]="preset"
          style="width: 20px; height: 20px; border: 1px solid #ccc; cursor: pointer;"
          (click)="selectPreset(preset)"
        ></button>
        }
      </div>
    </div>
  `,
  standalone: false,
})
export class ColorPickerEditorEnhancedComponent extends HotCellEditorAdvancedComponent<string> {
  presetColors = ["#FF0000", "#00FF00", "#0000FF", "#FFFF00", "#FF00FF", "#00FFFF"];

  override afterClose(): void {
    this.finishEdit.emit();
  }

  onColorChange(event: Event): void {
    const input = event.target as HTMLInputElement;
    this.setValue(input.value);
  }

  selectPreset(color: string): void {
    this.setValue(color);
  }
}

2. Custom Styling for Invalid Values

Highlight invalid colors in the renderer:

@Component({
  selector: "app-color-renderer-validated",
  changeDetection: ChangeDetectionStrategy.OnPush,
  template: `
    <div
      style="height: 100%; width: 100%; display: flex; align-items: center; justify-content: center;"
      [style.background]="isValid ? value : '#f0f0f0'"
      [style.color]="isValid ? '#000' : '#ff0000'"
    >
      <b>{{ isValid ? value : "Invalid Color" }}</b>
    </div>
  `,
  styles: `:host { height: 100%; width: 100%; }`,
})
export class ColorRendererValidatedComponent extends HotCellRendererAdvancedComponent<string> {
  get isValid(): boolean {
    return /^#[0-9A-Fa-f]{6}$/.test(this.value);
  }
}

3. Add Color Name Tooltip

Display color name on hover:

@Component({
  selector: "app-color-renderer-tooltip",
  changeDetection: ChangeDetectionStrategy.OnPush,
  template: `
    <div style="height: 100%; width: 100%;" [style.background]="value" [title]="getColorName()">
      <b>{{ value }}</b>
    </div>
  `,
  styles: `:host { height: 100%; width: 100%; }`,
})
export class ColorRendererTooltipComponent extends HotCellRendererAdvancedComponent<string> {
  getColorName(): string {
    const colorNames: Record<string, string> = {
      "#FF0000": "Red",
      "#00FF00": "Green",
      "#0000FF": "Blue",
      // Add more colors...
    };
    return colorNames[this.value.toUpperCase()] || this.value;
  }
}

4. Support RGB Format

Extend validator to support RGB colors:

const flexibleColorValidator = (value: string): boolean => {
  // Support both hex and rgb formats
  const hexRegex = /^#[0-9A-Fa-f]{6}$/;
  const rgbRegex = /^rgb\(\d{1,3},\s*\d{1,3},\s*\d{1,3}\)$/;

  return hexRegex.test(value) || rgbRegex.test(value);
};

// Update column config
{
  data: "color",
  editor: ColorPickerEditorComponent,
  renderer: ColorRendererComponent,
  validator: flexibleColorValidator,
}

Note: Native <input type="color"> always outputs hex format, so you'd need a custom text input editor to allow RGB input.

5. Add Renderer Props

Pass configuration to renderer via rendererProps:

@Component({
  selector: "app-color-renderer-configurable",
  changeDetection: ChangeDetectionStrategy.OnPush,
  template: `
    <div
      style="height: 100%; width: 100%;"
      [style.background]="value"
      [style.border]="props.showBorder ? '2px solid #333' : 'none'">
      @if (props.showLabel) {
        <b>{{ value }}</b>
      }
    </div>
  `,
  styles: `:host { height: 100%; width: 100%; }`,
})
export class ColorRendererConfigurableComponent extends HotCellRendererAdvancedComponent<string, { showLabel: boolean; showBorder: boolean }> {
  get props() {
    return this.getProps();
  }
}

// In column config:
{
  data: "color",
  editor: ColorPickerEditorComponent,
  renderer: ColorRendererConfigurableComponent,
  rendererProps: {
    showLabel: true,
    showBorder: false,
  },
}

Congratulations! You've created a fully functional color picker cell in Angular using the native HTML5 color input, with a circle swatch renderer and hex validation. For a Pickr-based color picker (button + nano theme), see the JavaScript Color Picker recipe.