diff --git a/src/uix/PENDIENTES.md b/src/uix/PENDIENTES.md
index b6f3241c2..cb320948e 100644
--- a/src/uix/PENDIENTES.md
+++ b/src/uix/PENDIENTES.md
@@ -168,3 +168,35 @@ Footer, ToolbarItem, etc.).
Demos: las parts se envuelven en `{#if showX}` con state local del
demo para dar el control UI, pero las parts en sí no leen ese state.
+
+### N-8 · Reutilizar componentes ya definidos en componentes complejos
+
+Cuando un componente complejo (picker, combobox, color-picker, etc.)
+necesita un sub-control que ya existe como componente independiente
+(Slider, Popover, Calendar, Field, etc.), **debe componerlo en vez de
+reimplementarlo**. Sólo se justifica un fork si reusarlo causaría una
+pérdida de características concreta y demostrable.
+
+Beneficios:
+
+1. **Una sola fuente de eventos sema**. El sub-control emite sus
+ eventos canónicos (`slider:handle-pick`, `slider:commit-set`,
+ `popover:close-dismiss`, etc.) que el composite hereda
+ automáticamente — sin reimplementar la dimensión perceptiva.
+2. **Una sola implementación de pointer/keyboard/ARIA**. Bugs
+ corregidos en el componente raíz se propagan a todos los composites
+ que lo usan. Sin duplicación de drag, focus trap, etc.
+3. **Tokens del recipe unificados**. `--slider-track-size`,
+ `--popover-content-padding`, etc. se setean en un único recipe;
+ todos los composites consumen los mismos.
+4. **Superficie pública estable**. La API del composite (parts +
+ eventos) se reduce a su lógica diferenciadora; el resto cae del
+ sub-control.
+
+Caso aplicado 2026-05-21: ColorPicker.ChannelSlider deja de tener su
+propio `ColorPickerChannelSliderProvider` con pointer handlers
+duplicados; ahora compone `SliderProvider` por debajo. Channel-aware
+behavior queda en una sola función bridge (`setChannel` + gradient
+inline en `--cp-channel-gradient`). Resultado: −200 líneas en soma,
+eventos `slider:handle-pick/drag/commit-set` propagados, tokens
+unificados con time-picker / standalone Slider.
diff --git a/src/uix/eidos/components/color-picker/color-picker-channel-slider-thumb.svelte b/src/uix/eidos/components/color-picker/color-picker-channel-slider-thumb.svelte
deleted file mode 100644
index f824ca345..000000000
--- a/src/uix/eidos/components/color-picker/color-picker-channel-slider-thumb.svelte
+++ /dev/null
@@ -1,8 +0,0 @@
-
-
-{@render children?.()}
diff --git a/src/uix/eidos/components/color-picker/color-picker-channel-slider-track.svelte b/src/uix/eidos/components/color-picker/color-picker-channel-slider-track.svelte
deleted file mode 100644
index 54480690a..000000000
--- a/src/uix/eidos/components/color-picker/color-picker-channel-slider-track.svelte
+++ /dev/null
@@ -1,8 +0,0 @@
-
-
-{@render children?.()}
diff --git a/src/uix/eidos/components/color-picker/color-picker-channel-slider.svelte b/src/uix/eidos/components/color-picker/color-picker-channel-slider.svelte
index b6eff2aab..5a0eb4cb9 100644
--- a/src/uix/eidos/components/color-picker/color-picker-channel-slider.svelte
+++ b/src/uix/eidos/components/color-picker/color-picker-channel-slider.svelte
@@ -1,5 +1,6 @@
-
-{#if child}
- {@render child({ props: mergedProps })}
-{:else}
-
{@render children?.()}
-{/if}
diff --git a/src/uix/soma/components/color-picker/components/color-picker-channel-slider-track.svelte b/src/uix/soma/components/color-picker/components/color-picker-channel-slider-track.svelte
deleted file mode 100644
index 5f1c33d15..000000000
--- a/src/uix/soma/components/color-picker/components/color-picker-channel-slider-track.svelte
+++ /dev/null
@@ -1,36 +0,0 @@
-
-
-{#if child}
- {@render child({ props: mergedProps })}
-{:else}
- {@render children?.()}
-{/if}
diff --git a/src/uix/soma/components/color-picker/components/color-picker-channel-slider.svelte b/src/uix/soma/components/color-picker/components/color-picker-channel-slider.svelte
index 1abe53f64..77cf67b37 100644
--- a/src/uix/soma/components/color-picker/components/color-picker-channel-slider.svelte
+++ b/src/uix/soma/components/color-picker/components/color-picker-channel-slider.svelte
@@ -1,11 +1,14 @@
{#if child}
- {@render child({ props: mergedProps, ...snippetProps })}
+ {@render child({ props: mergedProps, channel })}
{:else}
- {@render children?.(snippetProps)}
+ {@render children?.({ channel })}
{/if}
diff --git a/src/uix/soma/components/color-picker/exports.ts b/src/uix/soma/components/color-picker/exports.ts
index 20b949fa4..46d45a503 100644
--- a/src/uix/soma/components/color-picker/exports.ts
+++ b/src/uix/soma/components/color-picker/exports.ts
@@ -16,10 +16,17 @@ export { default as AreaBackground } from './components/color-picker-area-backgr
export { default as AreaThumb } from './components/color-picker-area-thumb.svelte';
export { default as TransparencyGrid } from './components/color-picker-transparency-grid.svelte';
-// Stage 3 — Channel sliders (1D slider per channel: hue / saturation / alpha / red / …)
+// Stage 3 — Channel sliders.
+// The ChannelSlider composes the generic SliderProvider underneath (see
+// `components/color-picker-channel-slider.svelte`). The Track / Thumb roles
+// are filled by the slider's `Range` / `Thumb` parts directly — re-exported
+// here so the public API stays `ColorPicker.ChannelSliderTrack` /
+// `ColorPicker.ChannelSliderThumb` for consumers that compose them inline.
export { default as ChannelSlider } from './components/color-picker-channel-slider.svelte';
-export { default as ChannelSliderTrack } from './components/color-picker-channel-slider-track.svelte';
-export { default as ChannelSliderThumb } from './components/color-picker-channel-slider-thumb.svelte';
+export {
+ Range as ChannelSliderTrack,
+ Thumb as ChannelSliderThumb
+} from '../slider/exports';
// Stage 5 — Swatches (radiogroup of preset colors)
export { default as SwatchGroup } from './components/color-picker-swatch-group.svelte';
@@ -57,8 +64,6 @@ export type {
ColorPickerAreaSnippetProps as AreaSnippetProps,
ColorPickerTransparencyGridProps as TransparencyGridProps,
ColorPickerChannelSliderProps as ChannelSliderProps,
- ColorPickerChannelSliderTrackProps as ChannelSliderTrackProps,
- ColorPickerChannelSliderThumbProps as ChannelSliderThumbProps,
ColorPickerChannelSliderSnippetProps as ChannelSliderSnippetProps,
ColorPickerSwatchGroupProps as SwatchGroupProps,
ColorPickerSwatchTriggerProps as SwatchTriggerProps,
@@ -74,3 +79,9 @@ export type {
ColorPickerFormatSelectProps as FormatSelectProps,
ColorChannel
} from './types';
+
+// Re-export slider props for the inner thumb/track tags.
+export type {
+ SliderRangeProps as ChannelSliderTrackProps,
+ SliderThumbProps as ChannelSliderThumbProps
+} from '../slider/types';
diff --git a/src/uix/soma/components/color-picker/types.ts b/src/uix/soma/components/color-picker/types.ts
index 07efd40fc..cccf5630f 100644
--- a/src/uix/soma/components/color-picker/types.ts
+++ b/src/uix/soma/components/color-picker/types.ts
@@ -37,14 +37,17 @@ export type ColorPickerAreaSnippetProps = {
thumbStyle: { left: string; top: string };
};
-/** Snippet props exposed by `ColorPicker.ChannelSlider`. */
+/**
+ * Snippet props exposed by `ColorPicker.ChannelSlider`.
+ *
+ * As of 2026-05-21 the channel slider composes the generic `SliderProvider`
+ * internally; the gradient + thumb position are wired automatically through
+ * the slider's own DOM ([data-slider]::before for the track, the gradient
+ * lives on the wrapper as `--cp-channel-gradient`). Consumers only need
+ * the `channel` discriminator to label / branch on.
+ */
export type ColorPickerChannelSliderSnippetProps = {
- /** Current normalized value (0..1) along the slider. */
- progress: number;
- /** CSS thumb position (left for horizontal, top for vertical). */
- thumbStyle: { left: string } | { top: string };
- /** Gradient string representing the channel's full range under the current color. */
- backgroundStyle: string;
+ channel: ColorChannel;
};
// ── Root provider ──────────────────────────────────────────────────────────
@@ -203,7 +206,15 @@ export type ColorPickerAreaThumbProps = WithChild<{ id?: string }> &
// ── ChannelSlider ──────────────────────────────────────────────────────────
-/** Props for `ColorPicker.ChannelSlider` — 1D slider bound to a single channel. */
+/**
+ * Props for `ColorPicker.ChannelSlider` — 1D slider bound to a single channel.
+ *
+ * Internally composes the generic `SliderProvider`. Track / Thumb are
+ * re-exported from `Slider` as `ChannelSliderTrack` / `ChannelSliderThumb`
+ * — they carry the canonical `[data-slider-range]` / `[data-slider-thumb]`
+ * markers and emit the slider's sema events (`handle-pick`, `handle-drag`,
+ * `commit-set`).
+ */
export type ColorPickerChannelSliderProps = WithChild<
{
id?: string;
@@ -212,16 +223,10 @@ export type ColorPickerChannelSliderProps = WithChild<
/** Orientation. @default 'horizontal' */
orientation?: Orientation;
},
- ColorPickerChannelSliderSnippetProps
+ { channel: ColorChannel }
> &
Without;
-export type ColorPickerChannelSliderTrackProps = WithChild<{ id?: string }> &
- Without;
-
-export type ColorPickerChannelSliderThumbProps = WithChild<{ id?: string }> &
- Without;
-
// ── TransparencyGrid ───────────────────────────────────────────────────────
/** Props for `ColorPicker.TransparencyGrid` — decorative checkerboard under alpha. */