oTree Forum >

Sliders with feedback and tick labels without anchoring

#1 by Juanfran

Hi all,

I am looking for a slider with no anchor but that allowing to have tick marks (with their correspondent labels).

I found in the forum this nice slider from Max Grossman: https://gitlab.com/gr0ssmann/otree_slider, which is inspired in Victor van Pelt slider (https://www.accountingexperiments.com/post/sliders/).

However, I cannot manage to include the tick marks (with their labels) in any of those solutions.

More specifically, I am trying to have a slider that goes from 0 to 100 without any initial anchor that includes tickmarks of step 10 (i.e., a tick mark at 10, at 20,, at 30...). I have been also trying to change the Real-time Feedback so that it is presented above the circle/square of the slider (instead of a whole big sentence at the top/bottom). My problem is dealing with CSS/JS, since my knowledge of those languages is little.

Could anyone help me with this? I believe this would be a good output for the whole community.


Juanfran Blazquiz

#2 by gr0ssmann (edited )

Both of these things are not easy to achieve. Here are some pointers.

Regarding the tick marks: To this day in 2023, it is difficult to style HTML sliders (<input type="range">). More info here: https://stackoverflow.com/questions/26612700/ticks-for-type-range-html-input

Once you have settled on a solution, you can access the <input type="range"> of my sliders using the selector .mgslider input, e.g. using CSS and/or JavaScript.

I want to note, however, that you can set the step argument. However, if you do not want to restrict subjects but only show them some visual guidance, there might not be a good solution.

Putting the value above the "knob" is also not entirely trivial, but I believe it is simpler to resolve than the previous question.

First of all, you should hide the default feedback, using:

    .mgslider-feedback {
        display: none;

Then you can create a "hook" method. This method is called by my slider after a new value is inputted. For example, in the example on GitLab (https://gitlab.com/gr0ssmann/otree_slider/-/blob/main/slider/SliderPage.html), you could put

    slider1.hook = function (slider, value) {

Every time slider1 is moved, you will see an alert with the current value. Clearly, you can use this functionality to put the current value above the knob. Here is an example: https://stackoverflow.com/a/62820462

This would necessitate creating a <span> directly above the <input>. This could be added above line 87 in the mgslider.js (https://gitlab.com/gr0ssmann/otree_slider/-/blob/main/mgslider.js), which defines the markup for my sliders. (Of course, there are many different methods to create a <span> before the <input>.)

#3 by Juanfran (edited )

I see your point. I found in some pages that adding tick marks is possible using list (see https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input/range#adding_labels). However, I cannot manage to add this to the code you developed (it is out of my scope that JS level).

Could you please tell me how to add the code in the example you shared to the slider1.hook? I guess that the function should be added below the line "slider1.print(document.getElementById("slider1_here"));" (at least, doing so worked to me with the alert(value) solution you provided, what I am not able to do is to set it as a number above the bar). I found the following resource https://css-tricks.com/value-bubbles-for-range-inputs/ but I don't manage to convert it suing the equivalent definitions to your code. Also, another possible solution to me (in case it's easier) would be to display the value in a different position along the page (not necessarily above the slider bar). I adapted my code to have your sliders in some cells of an HTML table, so leaving the value in a different cell would also be cool.

#4 by gr0ssmann

If you send me an MWE (https://en.wikipedia.org/wiki/Minimal_reproducible_example) of your page, I can try.

#5 by Juanfran

Below you have the HMTL (including HTML, CSS and JS) code of an MWE.
Let me know if there is anything else you need. Thank you very much in advance.

{{ block styles }}
        .otree-title {
        //    text-align: center;
        //    display:none;
        //    visibility:hidden;
              padding-bottom: 10px;
        .otree-body {
            text-align: justify;
            text-justify: inter-word;
        button.link { background:none;border:none; }

    .table>tbody>tr>td { /* button cells */
        padding: 1.33rem;
        padding-bottom: 0.5rem;
        padding-top: 0.5rem;
        text-align: center;
        border: 1px solid #dee2e6;
        font-weight: normal;

     table>thead {
        border: 1px solid #dee2e6;

    #overflow-table {

    @media screen and (max-width: 850px) {
      #overflow-table {

    .mgslider-wrapper {
    border-spacing: 10px;
    .mgslider-limit {
        width: 10%;
        min-width: 40px;
        height: 40px;
        margin: 100px;
        text-align: center;
        background: #eee;
        border: 1px solid #888;
    .mgslider-limit, .mgslider-value {
        font-variant-numeric: tabular-nums;
    .mgslider-before {
        height: 10px;
        width: 100%;
        // background: #1e5bff;
        background: #eee;
    .mgslider-feedback {
        -webkit-user-select: none;
        -ms-user-select: none;
        user-select: none;
{{ endblock }}

{{ block title }}
{{ endblock }}

{{ block content }}
<p class="text-justify" style="padding-bottom: 10px;">
    In the table below, we ask you to choose a rate for each item.
    Please, enter a number (from 0 to 100) by clicking and dragging the correspondent sliders below.

<table class="table table-bordered text-center" style="width: auto; margin: auto; table-layout: fixed;">
            <th style="width:30%"> </th>
            <th style="width:70%; background-color:#efefef">&nbsp; Your rate &nbsp;</th>

            <th style="vertical-align: middle; height:80px">
                Item 1: &nbsp; <button type="button" class="button circle-filled" style="vertical-align:-14px;"> </button>
                {{ formfield_errors 'item1' }}
            <td style="vertical-align: middle; font-size: 25px">
                <div id="slider1_here"></div>
            <th style="vertical-align: middle; height:80px">
                Item 2: 
                {{ formfield_errors 'item2' }}
            <td style="vertical-align: middle; font-size: 25px">
                <div id="slider2_here"></div>

<script src="{{ static 'mgslider.js' }}"></script>
    $(document).ready(function (event) {
        slider1 = new mgslider("item1", 0, 100, 1);

    $(document).ready(function (event) {
        slider2 = new mgslider("item2", 0, 100, 1);

<p style="padding-bottom: 20px;"> </p>

<p class="text-justify">
    Please, press the next button after expressing all your decisions.

    {{ next_button }}

{{ endblock }}

#6 by gr0ssmann

Hi, do you still need this?

#7 by Juanfran


Yes, I still need it.

#8 by gr0ssmann

I am unfortunately unable to achieve this. The tick marks and the bubble do not show and I was not able to debug it. I fear this may not be possible with my sliders. I'd suggest you code your required sliders from scratch, e.g. a slider whose initial value is random.

#9 by Juanfran

Oh, no problem. I said it since, apart from a personal reason, I think this would be a good public good for the community.
Thank you very much for the help anyway!

#10 by BonnEconLab

Juanfran, in your HTML template, include the following CSS declarations:

    /* Based on https://stackoverflow.com/a/64306268 */
    .range {
        --ticksThickness: 2px;
        --ticksHeight: 30%;
        --ticksColor: silver;        
        display: inline-block;
        background: silver;
        background: linear-gradient(to right, var(--ticksColor) var(--ticksThickness), transparent 1px) repeat-x;
        width: 95%;
        background-size: calc(100%/((var(--max) - var(--min)) / var(--step)) - .1%) var(--ticksHeight);
        background-position: 0 bottom;
        position: relative;
    /* min / max labels at the edges */
    /* .range::before, .range::after {
        font: 12px monospace;
        content: counter(x);
        position: absolute;
        bottom: -2ch;
    } */
    .range::before {
        counter-reset: x var(--min);
        transform: translateX(-50%);
    .range::after {
        counter-reset: x var(--max);
        right: 0;
        transform: translateX(50%);
    .range > input {
        width: 101%;
        margin: 0 -6px;
        margin-left: -0.75%;

In gr0ssmann’s mgslider.js, adapt the following two functions:

mgslider.prototype.markup = function () {
    return "\
        <table id='" + this.id("wrapper") + "' class='mgslider-wrapper' border='0'>\
            <tr class='mgslider-feedback'>\
                <td id='" + this.id("show") + "' class='mgslider-show' colspan='3'><b><span id='" + this.id("cur") + "' class='mgslider-value'></span></b></td>\
                <td class='mgslider-limit'>" + this.f2s(this.min, true) + "</td>\
                <td width='90%'>\
                    <div id='" + this.id("before") + "' class='mgslider-before' onclick='mgsliders.lookup(\"" + this.field + "\").reveal(event)'></div>\
                    <div class='range' style='--step:10; --min:" + this.min + "; --max:" + this.max + "'>\
                        <input type='range' id='" + this.id() + "' min='" + this.min + "' max='" + this.max + "' step='" + this.step + "' value='' class='mgslider form-range' oninput='mgsliders.lookup(\"" + this.field + "\").change()' onchange='mgsliders.lookup(\"" + this.field + "\").change()'>\
                <td class='mgslider-limit'>" + this.f2s(this.max, true) + "</td>\
        <input type='hidden' id='" + this.id("input") + "' name='" + this.field + "' value='' />";

mgslider.prototype.change = function (target, omit_hook) {
    if (typeof target === "undefined") {
        var value = this.value();
    else {
        var value = target;

        document.getElementById(this.id()).value = value;
    document.getElementById(this.id("cur")).innerHTML = this.f2s(value, false);
    document.getElementById(this.id("cur")).style.marginLeft = (1.53 * (value - (this.max - this.min) / 2) + 1.5) + "%";
    document.getElementById(this.id("input")).value = value;
    if (omit_hook !== true) {
        return this.hook(this, value);

You may have to fine-tune some of the numerical values (that determine marginLeft) for your purposes.

#11 by BonnEconLab

Here’s a screenshot of the resulting sliders with tick marks and an indicator of the currently chosen value that is located right above the slider’s “thumb.”

Write a reply

Set forum username