Skip to content

ColorPicker

ColorPicker folds the web contract’s eight parts (Root, Area, HueSlider, AlphaSlider, Field, Swatches, SwatchItem, Swatch) into one lookless Control that owns the color directly as HSVA dependency properties (Hue, Saturation, Brightness, Alpha) and drives named template parts: a 2D area thumb, hue and alpha track thumbs, a hex TextBox, and a native ListBox for swatches. Pointer interaction rides WPF’s native Thumb/DragDelta mouse-capture machinery instead of a custom 2D pointer tracker, and the swatch list reuses native ListBox keyboard navigation instead of a bespoke roving-tabindex coordinator.

xmlns:colorpicker="clr-namespace:Navius.Wpf.Primitives.Controls;assembly=Navius.Wpf.Primitives"
<colorpicker:NaviusColorPicker x:Name="Picker"
Value="{Binding SelectedColor, Mode=TwoWay}"
Format="hex"
AlphaEnabled="True"
Colors="{Binding Presets}" />
Property Type Default From Description
Value string? null NaviusColorPicker The controlled color string, projected via Format. Two-way bindable by default.
DefaultValue string? null NaviusColorPicker Uncontrolled initial color string, applied once on Loaded when Value is unset.
Format string "hex" NaviusColorPicker Output format for Value: hex, rgb, rgba, hsl, or hsla.
AlphaEnabled bool false NaviusColorPicker Whether alpha is reflected in the projected Value string.
ReadOnly bool false NaviusColorPicker Blocks all interactive mutation paths (tracks, hex field, swatch selection) while still displaying the current color.
FieldName string? null NaviusColorPicker API-parity only, mirroring the web’s Name. Named FieldName (not Name) since FrameworkElement.Name is already a DP with XAML-special x:Name/FindName meaning; the web’s hidden bubble-input form participation has no WPF equivalent and is dropped.
Colors IReadOnlyList<string>? null NaviusColorPicker Preset color strings rendered as swatch list items.
AreaAriaLabel string "Color" NaviusColorPicker Accessible name for the area thumb. Consumer-overridable dependency property (M6 audit: previously hardcoded into the theme with no override seam; fixed).
FieldAriaLabel string "Hex color" NaviusColorPicker Accessible name for the hex TextBox. Same M6 fix as AreaAriaLabel.
SwatchesAriaLabel string "Swatches" NaviusColorPicker Accessible name for the swatches list. Same M6 fix as AreaAriaLabel.
Hue double 0.0 NaviusColorPicker Degrees, coerced into [0, 360]. The literal value 360 is preserved rather than wrapped to 0 (M6 fix, see Web deltas); every other out-of-range value still wraps via modulo.
Saturation double 0.0 NaviusColorPicker Fraction, coerced into [0, 1].
Brightness double 1.0 NaviusColorPicker HSV’s “V”. Fraction, coerced into [0, 1].
Alpha double 1.0 NaviusColorPicker Fraction, coerced into [0, 1]. Only reflected in Value when AlphaEnabled.
HexValue string (read-only) - NaviusColorPicker The current color as hex regardless of Format; mirrors the web’s ColorPickerContext.HexValue.
Event Signature Fires when
ValueChanged RoutedPropertyChangedEventHandler<string?> Value changes, whether driven by the HSVA model (a track drag, keyboard nudge, hex commit, or swatch selection) or by an external caller setting Value directly.
Key Behavior Part
ArrowRight / ArrowLeft Saturation += / -= step (0.1 with Shift) Area thumb
ArrowUp / ArrowDown Brightness += / -= step (0.1 with Shift) Area thumb
PageUp / PageDown Brightness += / -= large step Area thumb
Home / End Saturation = 0 / 1 Area thumb
ArrowRight / ArrowUp Hue += 1 degree (10 with Shift) Hue thumb
ArrowLeft / ArrowDown Hue -= 1 degree (10 with Shift) Hue thumb
PageUp / PageDown Hue += / -= 10 Hue thumb
Home / End Hue = 0 / 360 Hue thumb
ArrowRight / ArrowUp Alpha += 0.01 (0.1 with Shift) Alpha thumb
ArrowLeft / ArrowDown Alpha -= 0.01 (0.1 with Shift) Alpha thumb
PageUp / PageDown Alpha += / -= 0.1 Alpha thumb
Home / End Alpha = 0 / 1 Alpha thumb
Enter (hex field) Commits the field’s text through ColorMath.TryParse; an unparsable value reverts the field’s displayed text. Hex field
Native ListBox navigation (arrows, Home, End) Roving selection over swatches Swatches list

The hex field also commits on LostFocus. All track keyboard handlers and pointer-drag handlers no-op when the control is disabled or ReadOnly (IsInteractive()).

NaviusColorPickerAutomationPeer (a FrameworkElementAutomationPeer) reports AutomationControlType.Group and implements a read-only IValueProvider, surfacing HexValue since the color otherwise lives only in template parts that expose nothing over UIA on their own; SetValue always throws. The peer overrides GetPattern to actually route PatternInterface.Value to itself: an M6-audit-confirmed and fixed bug found that IValueProvider was implemented but never wired through GetPattern, so ValuePattern was unreachable from any real UIA client despite appearing complete in code (WPF’s base GetPattern does not auto-detect implemented provider interfaces). The area, hue, and alpha thumbs are plain System.Windows.Controls.Primitives.Thumb instances with only a hardcoded AutomationProperties.Name; keyboard nudging changes the underlying values correctly, but none of the three thumbs expose IRangeValueProvider or a custom automation peer, so a screen reader gets no value/range announcement on any of them. This is a documented, unfixed gap from the M6 audit, not a silent one: closing it needs dedicated Thumb-derived classes or peer factories for all three tracks, judged a larger structural change than the audit’s remaining scope. The unit suite (tests/Navius.Wpf.Tests/ColorPickerTests.cs, 41 test methods) exercises the HSVA coercion rules, the Value/model sync in both directions, the hue-360-preservation fix, the GetPattern fix, and the AreaAriaLabel/FieldAriaLabel/SwatchesAriaLabel defaults and their consumer-overridability.

  • The HSVA-as-source-of-truth model was kept (an explicit resolution of the parity doc’s open question), exposed directly as Hue/Saturation/Brightness/Alpha dependency properties rather than translating to System.Windows.Media.Color (ARGB).
  • The area’s screen-reader-only brightness (y) thumb from the web contract is not ported as a second hidden peer: this port renders one visible 2D thumb with one automation peer rather than two separate hidden/visible role="slider" elements.
  • The swatches list reuses native ListBox keyboard navigation (roving selection, arrows, Home/End) rather than reimplementing the web’s bespoke SwatchesContext roving-tabindex coordinator.
  • Pointer drag uses WPF’s native Thumb.DragDelta (mouse capture) instead of the web’s JS 2D pointer tracker.
  • NaviusColorPickerSwatches.ChildContent (explicit custom swatch items overriding auto-generated items from Colors) has no WPF equivalent: the swatches ListBox is unconditionally bound to Colors. Documented as a confirmed, disclosed gap from the M6 audit, not fixed in this pass.
  • The web’s hidden-bubble-input form participation (the Name parameter submitting the value natively) has no WPF analog and is dropped; FieldName survives only as an API-parity-only property with no functional effect.
  • Known residual (M6 audit, documented, not fixed): the Area/Hue/Alpha thumbs expose zero UIA value/range semantics (no IRangeValueProvider), despite the web contract’s role="slider" plus aria-valuemin/aria-valuemax/aria-valuenow/aria-valuetext on each corresponding element.
  • Fixed in the M6 audit: the Hue thumb’s End key previously produced the same coerced model value as Home (CoerceHue unconditionally wrapped 360 to 0), visually snapping the thumb to the wrong edge; CoerceHue now preserves the literal 360 boundary (safe, since hue-to-RGB conversion re-normalizes internally, so 0 and 360 render identically) while all other out-of-range values still wrap via modulo.
  • Fixed in the M6 audit: the current-color preview swatch had no AutomationProperties.Name at all, contradicting the web contract’s role="img" with aria-label set to the projected color string; now bound to the picker’s Value.

The ColorPicker Gallery page rendered at the pinned commit, in each theme.

ColorPicker Gallery page in the light theme

ColorPicker Gallery page in the dark theme

ColorPicker Gallery page in the high contrast theme