Gradio custom HTML component: Likert Scale
Author: gradio
Category: input
Tags: input, survey, scale
Description: Agreement scale from Strongly Disagree to Strongly Agree

Head:

HTML template:
<div class="likert-container">
    <div class="likert-question">${question}</div>
    <div class="likert-scale">
        ${['Strongly Disagree', 'Disagree', 'Neutral', 'Agree', 'Strongly Agree'].map((label, i) => `
            <label class="likert-option ${value === i + 1 ? 'selected' : ''}">
                <input type="radio" name="likert" value="${i + 1}" ${value === i + 1 ? 'checked' : ''} />
                <span class="likert-dot"></span>
                <span class="likert-label">${label}</span>
            </label>
        `).join('')}
    </div>
</div>

CSS:
.likert-container { padding: 8px 0; }
.likert-question { font-weight: 600; margin-bottom: 16px; font-size: 15px; }
.likert-scale { display: flex; justify-content: space-between; gap: 4px; }
.likert-option { display: flex; flex-direction: column; align-items: center; cursor: pointer; flex: 1; gap: 8px; }
.likert-option input { display: none; }
.likert-dot { width: 24px; height: 24px; border-radius: 50%; border: 2px solid #d1d5db; transition: all 0.2s; }
.likert-option:hover .likert-dot { border-color: #f97316; }
.likert-option.selected .likert-dot { background: #f97316; border-color: #f97316; }
.likert-label { font-size: 11px; text-align: center; color: #6b7280; line-height: 1.3; }

JavaScript (on load):
const radios = element.querySelectorAll('input[type="radio"]');
radios.forEach(radio => {
    radio.addEventListener('change', () => {
        props.value = parseInt(radio.value);
    });
});

Python code:
class LikertScale(gr.HTML):
    def __init__(self, question="Rate this item", value=0, **kwargs):
        html_template = """
        <div class="likert-container">
            <div class="likert-question">${question}</div>
            <div class="likert-scale">
                ${['Strongly Disagree', 'Disagree', 'Neutral', 'Agree', 'Strongly Agree'].map((label, i) => `
                    <label class="likert-option ${value === i + 1 ? 'selected' : ''}">
                        <input type="radio" name="likert" value="${i + 1}" ${value === i + 1 ? 'checked' : ''} />
                        <span class="likert-dot"></span>
                        <span class="likert-label">${label}</span>
                    </label>
                `).join('')}
            </div>
        </div>
        """
        css_template = """
        .likert-scale { display: flex; justify-content: space-between; }
        .likert-option { display: flex; flex-direction: column; align-items: center; cursor: pointer; }
        .likert-dot { width: 24px; height: 24px; border-radius: 50%; border: 2px solid #d1d5db; }
        .likert-option.selected .likert-dot { background: #f97316; border-color: #f97316; }
        """
        js_on_load = """
        const radios = element.querySelectorAll('input[type="radio"]');
        radios.forEach(radio => {
            radio.addEventListener('change', () => {
                props.value = parseInt(radio.value);
            });
        });
        """
        super().__init__(
            value=value, question=question,
            html_template=html_template,
            css_template=css_template,
            js_on_load=js_on_load, **kwargs
        )

    def api_info(self):
        return {"type": "integer", "minimum": 0, "maximum": 5}