Basic
Single value slider with tooltip.
Code
<%= render HakumiComponents::Slider::Component.new(
name: "volume",
value: 30,
min: 0,
max: 100,
step: 5,
tooltip_visible: true
) %>
Range
Two handles for selecting a range.
Code
<%= render HakumiComponents::Slider::Component.new(
name: "range",
range: true,
value: [20, 80],
min: 0,
max: 100,
step: 5,
tooltip_visible: true
) %>
Vertical
Vertical slider layout.
Code
<div style="height: 240px;">
<%= render HakumiComponents::Slider::Component.new(
name: "vertical_volume",
vertical: true,
value: 40,
min: 0,
max: 100,
step: 10,
tooltip_visible: true
) %>
</div>
Marks & Dots
Show marks and step dots.
Code
<%= render HakumiComponents::Slider::Component.new(
name: "marks",
value: 40,
min: 0,
max: 100,
step: 10,
marks: {
0 => "0",
20 => "20",
40 => "40",
60 => "60",
80 => "80",
100 => "100"
},
dots: true,
tooltip_visible: true
) %>
Disabled
Disabled slider state.
Toggle disabled state via API:
Control value programmatically:
Code
<%= render HakumiComponents::Space::Component.new(size: :large, direction: :vertical, align: :start) do %>
<%= render HakumiComponents::Slider::Component.new(
name: "disabled_slider",
value: 60,
min: 0,
max: 100,
step: 5,
disabled: true,
tooltip_visible: true
) %>
<%= render HakumiComponents::Space::Component.new(size: :default, direction: :vertical, align: :start) do %>
<p style="margin-bottom: 8px;">Toggle disabled state via API:</p>
<%= render HakumiComponents::Space::Component.new(size: :default, align: :center) do %>
<div style="width: 200px; flex-shrink: 0;">
<%= render HakumiComponents::Slider::Component.new(
id: "slider-disabled-demo",
value: 30,
disabled: true
) %>
</div>
<%= render HakumiComponents::Button::Component.new(
size: :small,
onclick: "document.getElementById('slider-disabled-demo').hakumiComponent?.api.toggleDisabled()"
) do %>
Toggle Disabled
<% end %>
<% end %>
<% end %>
<%= render HakumiComponents::Space::Component.new(size: :default, direction: :vertical, align: :start) do %>
<p style="margin-bottom: 8px;">Control value programmatically:</p>
<%= render HakumiComponents::Space::Component.new(size: :default, align: :center) do %>
<div style="width: 200px; flex-shrink: 0;">
<%= render HakumiComponents::Slider::Component.new(
id: "slider-value-demo",
value: 50
) %>
</div>
<%= render HakumiComponents::Button::Component.new(
size: :small,
onclick: "document.getElementById('slider-value-demo').hakumiComponent?.api.setValue(0)"
) do %>
Set to 0
<% end %>
<%= render HakumiComponents::Button::Component.new(
size: :small,
onclick: "document.getElementById('slider-value-demo').hakumiComponent?.api.setValue(100)"
) do %>
Set to 100
<% end %>
<%= render HakumiComponents::Button::Component.new(
size: :small,
onclick: "document.getElementById('slider-value-demo').hakumiComponent?.api.reset()"
) do %>
Reset
<% end %>
<% end %>
<% end %>
<% end %>
Synchronized with InputNumber
Slider and InputNumber components synchronized via public API.
Both components stay in sync through their public APIs. Try dragging the slider or typing in the input.
Range slider synchronized with two inputs (inputs limit each other):
Code
<%= render HakumiComponents::Space::Component.new(size: :large, direction: :vertical) do %>
<p style="margin-bottom: 8px; color: var(--color-text-secondary);">
Both components stay in sync through their public APIs. Try dragging the slider or typing in the input.
</p>
<%= render HakumiComponents::Space::Component.new(size: :default, align: :center) do %>
<div style="flex: 1; min-width: 200px;">
<%= render HakumiComponents::Slider::Component.new(
id: "slider-sync-demo",
value: 50,
min: 0,
max: 100,
step: 1
) %>
</div>
<%= render HakumiComponents::InputNumber::Component.new(
name: "input_sync_demo",
id: "input-sync-demo",
value: 50,
min: 0,
max: 100,
step: 1,
style: "width: 80px;"
) %>
<% end %>
<%= render HakumiComponents::Space::Component.new(size: :default, direction: :vertical, align: :start) do %>
<p style="margin: 16px 0 8px; color: var(--color-text-secondary);">
Range slider synchronized with two inputs (inputs limit each other):
</p>
<%= render HakumiComponents::Space::Component.new(size: :default, align: :center) do %>
<%= render HakumiComponents::InputNumber::Component.new(
name: "range_min_demo",
id: "range-min-demo",
value: 20,
min: 0,
max: 80,
step: 1,
style: "width: 80px;"
) %>
<div style="flex: 1; min-width: 200px;">
<%= render HakumiComponents::Slider::Component.new(
id: "slider-range-sync-demo",
value: [20, 80],
range: true,
allow_cross: false,
min: 0,
max: 100,
step: 1,
handle_min: [0, 20],
handle_max: [80, 100]
) %>
</div>
<%= render HakumiComponents::InputNumber::Component.new(
name: "range_max_demo",
id: "range-max-demo",
value: 80,
min: 20,
max: 100,
step: 1,
style: "width: 80px;"
) %>
<% end %>
<% end %>
<% end %>
<script>
(function() {
requestAnimationFrame(function() {
setTimeout(function() {
// Flag to prevent infinite loops
let syncing = false;
// Single slider sync
const slider = document.getElementById('slider-sync-demo');
const input = document.getElementById('input-sync-demo');
if (slider && input) {
slider.addEventListener('hakumi--slider:change', function(e) {
if (syncing) return;
syncing = true;
if (input.hakumiComponent?.api) {
input.hakumiComponent?.api.setValue(e.detail.value);
}
syncing = false;
});
input.addEventListener('hakumi--input-number:change', function(e) {
if (syncing) return;
syncing = true;
if (slider.hakumiComponent?.api) {
slider.hakumiComponent?.api.setValue(e.detail.value);
}
syncing = false;
});
}
// Range slider sync with dynamic limits
let syncingRange = false;
const rangeSlider = document.getElementById('slider-range-sync-demo');
const minInput = document.getElementById('range-min-demo');
const maxInput = document.getElementById('range-max-demo');
const minInputEl = minInput?.querySelector('input');
const maxInputEl = maxInput?.querySelector('input');
if (rangeSlider && minInput && maxInput) {
// Update input limits based on current values
const updateInputLimits = function(minVal, maxVal) {
// Min input can go up to maxVal
if (minInputEl) minInputEl.max = maxVal;
// Max input can go down to minVal
if (maxInputEl) maxInputEl.min = minVal;
};
// Slider -> InputNumbers
rangeSlider.addEventListener('hakumi--slider:change', function(e) {
if (syncingRange) return;
syncingRange = true;
const [min, max] = e.detail.value;
if (minInput.hakumiComponent?.api) minInput.hakumiComponent?.api.setValue(min);
if (maxInput.hakumiComponent?.api) maxInput.hakumiComponent?.api.setValue(max);
updateInputLimits(min, max);
syncingRange = false;
});
// Min Input -> Slider
minInput.addEventListener('hakumi--input-number:change', function(e) {
if (syncingRange) return;
syncingRange = true;
const minVal = parseFloat(e.detail.value) || 0;
const maxVal = maxInput.hakumiComponent?.api?.getValue() || 100;
// Clamp min to not exceed max
const clampedMin = Math.min(minVal, maxVal);
if (rangeSlider.hakumiComponent?.api) {
rangeSlider.hakumiComponent?.api.setValue([clampedMin, maxVal]);
}
if (minVal !== clampedMin && minInput.hakumiComponent?.api) {
minInput.hakumiComponent?.api.setValue(clampedMin);
}
updateInputLimits(clampedMin, maxVal);
syncingRange = false;
});
// Max Input -> Slider
maxInput.addEventListener('hakumi--input-number:change', function(e) {
if (syncingRange) return;
syncingRange = true;
const maxVal = parseFloat(e.detail.value) || 100;
const minVal = minInput.hakumiComponent?.api?.getValue() || 0;
// Clamp max to not go below min
const clampedMax = Math.max(maxVal, minVal);
if (rangeSlider.hakumiComponent?.api) {
rangeSlider.hakumiComponent?.api.setValue([minVal, clampedMax]);
}
if (maxVal !== clampedMax && maxInput.hakumiComponent?.api) {
maxInput.hakumiComponent?.api.setValue(clampedMax);
}
updateInputLimits(minVal, clampedMax);
syncingRange = false;
});
}
}, 50);
});
})();
</script>
Slider Props
| Prop | Type | Default | Description |
|---|---|---|---|
name |
String or Symbol |
auto |
Input name/id. |
label |
String |
- |
Optional label above the control. |
caption |
String |
- |
Helper text displayed below. |
value |
Numeric or Array |
- |
Current value or [min, max]. |
default_value |
Numeric or Array |
- |
Initial value if value is nil. |
min |
Numeric |
0 |
Minimum value. |
max |
Numeric |
100 |
Maximum value. |
step |
Numeric |
1 |
Step size. |
range |
Boolean |
false |
Enable range handles. |
allow_cross |
Boolean |
true |
Allow range handles to cross each other. |
handle_min |
Array |
- |
Per-handle minimum values [leftMin, rightMin]. |
handle_max |
Array |
- |
Per-handle maximum values [leftMax, rightMax]. |
vertical |
Boolean |
false |
Vertical layout. |
marks |
Hash or Array |
- |
Mark labels per value. |
dots |
Boolean |
false |
Show dots at each step. |
tooltip_visible |
Boolean |
false |
Always show tooltip. |
keyboard |
Boolean |
true |
Enable keyboard control. |
disabled |
Boolean |
false |
Disable interactions. |
standalone |
Boolean |
true |
Render without FormItem wrapper. |
required |
Boolean |
false |
Add required attribute. |
errors |
Array[String] or String |
[] |
Error messages. |
**html_attrs |
Keyword args |
- |
`id`, `class`, `data`, etc. Standard HTML attributes passed to wrapper. |
JavaScript API
Access via element.hakumiComponent.api or HakumiComponents.get(element.id).
| Prop | Type | Default | Description |
|---|---|---|---|
getValue() |
Method |
- |
Return the current value, or [min, max] for range sliders. |
setValue(value) |
Method |
- |
Set the value, clamped and snapped to the configured range and step. |
reset() |
Method |
- |
Reset to default_value, or to the component minimum/maximum fallback. |
focus() |
Method |
- |
Focus the first handle. |
blur() |
Method |
- |
Blur all handles. |
setDisabled(disabled) |
Method |
- |
Set whether the slider is disabled. |
isDisabled() |
Method |
- |
Return true when the slider is disabled. |
toggleDisabled() |
Method |
- |
Toggle the disabled state. |
disable() |
Method |
- |
Disable the slider. |
enable() |
Method |
- |
Enable the slider. |
getMin() |
Method |
- |
Return the configured minimum value. |
getMax() |
Method |
- |
Return the configured maximum value. |
getStep() |
Method |
- |
Return the configured step size. |
isRange() |
Method |
- |
Return true when range mode is enabled. |
isVertical() |
Method |
- |
Return true when vertical layout is enabled. |