Color picker
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">)
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">
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().
- Handsontable’s modules are registered in the Angular module (see Step 5) via
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-celland.color-picker-swatchcenter and style the swatch;.color-picker-editorstyles the editor input.ChangeDetectionStrategy.OnPushis used to optimize performance.
- The template renders a circle swatch with the cell’s color via
Create the Editor Component
The editor component uses the native HTML5 color input. When the user selects a color, the value is updated and
afterClosecallsfinishEdit.emit()to save.@Component({selector: 'example1-color-picker-editor',template: `<inputclass="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 changeafterClose()callsfinishEdit.emit()so the value is saved when the editor closes
Lifecycle flow:
- User opens editor (double-click or F2)
- Input shows the current color
- User selects a color →
onColorChange()callssetValue(input.value) - User closes the editor (e.g. click outside) →
afterClose()runs →finishEdit.emit()saves the value
- Extends
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’sCustomValidatorFn<string>type - Uses regex to validate hex color format:
#followed by 6 hex characters - Returns
truefor valid colors like “#FF0000”, “#00ff00” - Returns
falsefor 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);- Simple function returning
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 modulesregisterAllModules();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
HotTableModulefor 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
HotTableModuleprovides the<hot-table>component- Global config applies to all Handsontable instances in the app
- Import
Configure Handsontable
Use the custom components in your Handsontable column configuration. The example adds a
colorproperty to each row (e.g. frominputData) 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 mappedcolorper row frominputData)[settings]="gridSettings"- passes configuration objecteditor: ColorPickerEditorComponent- uses custom editor classrenderer: 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
headerClassNamealign with the table layout
Enhancements
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);}}Custom Styling for Invalid Values
Highlight invalid colors in the renderer:
@Component({selector: "app-color-renderer-validated",changeDetection: ChangeDetectionStrategy.OnPush,template: `<divstyle="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);}}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;}}Support RGB Format
Extend validator to support RGB colors:
const flexibleColorValidator = (value: string): boolean => {// Support both hex and rgb formatsconst 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.Add Renderer Props
Pass configuration to renderer via
rendererProps:@Component({selector: "app-color-renderer-configurable",changeDetection: ChangeDetectionStrategy.OnPush,template: `<divstyle="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.