When and Why to Use
Use this when you need a numeric input within a defined range. It's best for:
- Age, year, count, or frequency questions
- Questions that require range-based recoding (e.g. age bands)
- Validating attention checks with specific values
Supports optional recoding and validation logic.
Chat Experience
- Respondent is shown a numeric input with increment/decrement buttons.
- Range boundaries are visible.
- If input is outside the range or invalid, an error message appears inline.
| With an image | Without an image |
|---|---|
![]() | ![]() |
Traditional Experience
- Same as chat, but with more space for contextual text or images.
- Navigation and focus-friendly for web-based interactions.
| With an image | With markdown | Mobile optimized |
|---|---|---|
![]() | ![]() | ![]() |
Configuration Options
| Option | Type | Required | Default | Description |
|---|---|---|---|---|
question | string | yes | - | The prompt shown to the user |
image | MediaItem | no | - | Optional image from s.media |
image_size | Tuple[int, int] | no | (600, 600) | Bounding box size for a displayed image |
min_max | Tuple[int, int] | no | (1, 100) | The valid numeric range for answers |
style | "input" | "button" | no | "input" | "button" shows recode ranges as buttons, storing the midpoint |
default | int | no | random between range | Default test data for QA |
recodes | dict[str, str] | no | - | Map ranges or percentages to buckets |
custom_validator | Callable[[str], str | None] | no | - | Function that returns a custom error message |
number_seconds | int | no | 0 | Seconds to wait before allowing the respondent to continue |
tags | s.tag() | no | - | Used to fill tokens in text and for reporting groupings |
id | string | no | - | Optional stable identifier for this question |
Example Code
Simple numeric:
s.numeric_question("What year were you born in?", min_max=(1900, 2024))
With recodes:
s.numeric_question( "How old are you?", min_max=(18, 100), recodes={ "18-25": "18-25", "26-54": "26-54", "55-74": "55-74", "75-100": "75+" } )
With percentage-based recodes:
s.numeric_question( "How many times have you visited our website?", min_max=(0, 100), recodes={ "0-30%": "Low", "31-70%": "Medium", "71-100%": "High" } )
With custom validator:
s.numeric_question( "How old are you?", min_max=(0, 120), custom_validator=lambda x: "Are you sure that you're 150 years old?" if x > 150 else None )
As range buttons:
s.numeric_question( "How old are you?", min_max=(18, 100), style="button", recodes={ "18-34": "18-34", "35-54": "35-54", "55-100": "55+" } )
With style="button", the recode ranges are shown as buttons instead of a numeric input, and the selected range stores its integer midpoint as the underlying value. Button style requires explicit finite-range recode keys — percentage and open-ended keys (like "0-50%" or "65+") are not supported in button style.
Recoding Numeric Responses
Sometimes it's useful to group numeric values into labeled categories - for instance, turning income ranges into brackets like "Low", "Middle", and "High". You define a set of rules that map numeric ranges to labels. Supported formats include:
- Fixed Ranges (e.g. "0-49" → "Low")
- Percentage Ranges (e.g. "0-25%")→ Interpreted relative to a known min and max.
- Open-ended Ranges (e.g. "80+")→ Interpreted as "80 and above".
Example 1: Satisfaction Score (0-100)
_recodes = { "0-25%": "Very Dissatisfied", "25-50%": "Dissatisfied", "50-75%": "Satisfied", "75-100%": "Very Satisfied" }
This scales the numeric value based on known min/max and assigns the appropriate label.
Example 2: Age Brackets
_recodes = { "0-17": "Underage", "18-64": "Adult", "65+": "Senior" }
In this case, "65+" means any value 65 or greater gets labeled "Senior".
What If No Match?
If the value doesn't fall into any defined range, the original number is returned unchanged.






