Documentation

Grid Select Question

When and Why to Use

Use this to collect structured responses across multiple attributes. Ideal for:

  • Marking multiple items on the same scale (e.g. product attributes)
  • Capturing matrix-style input efficiently
  • Comparing the same options across rows

Supports recoding, randomization, and media-based rows.

Chat Style
  • Each row is displayed as a vertical block with radio buttons or buttons, depending on style
  • Rows scroll vertically
  • Specify option input appears below the corresponding row when selected
Traditional Style
  • Grid is shown in tabular layout with rows as items and columns as options
  • Works well with keyboard/remote input
  • Hover/focus interaction highlights active cell
Chat style with buttonsTraditional with buttonsTraditional with RadioMobile optimized traditional
Markdown 2B ImagesPlain TextGrid Select Question Figure 01Grid Select Question Figure 02
Configuration Options
OptionTypeRequiredDefaultDescription
questionstringyes-The prompt shown to the user
rowsList[str | MediaItem]yes-Rows to show in the grid
row_namestringyes-Label used in reporting and data schema
optionsList[str | MediaItem]yes-Options available for each row
stylestringnoradio"radio" or "button" display style
randomizeboolnoFalseRandomize row order
randomize_optionsboolnoFalseRandomize option order per row
other_optionsList[str]no-Additional options to present to the user
fixed_optionsList[str]no-Options that are never randomized
specify_optionstrno-Adds an "Other" option to all rows
specify_textstrno"Please specify"Prompt for the specify input
imageMediaItemno-Image shown above the question
recodesDict[str, str]no-Maps options to alternate values
custom_validatorCallable[[Dict[str, str]], str | None]no-Function that returns a custom error message
image_label_fieldstrno-Field to use for labeling row images
show_image_labelboolnoTrueWhether to show labels under row images
image_sizeTuple[int, int]no-Pixel bounds for row images
skip_emptyboolnoFalseSkip question if rows are empty
defaultDict[str, str | List[str]]norandomDefault selections for test data
sortedLiteral['ascending', 'descending']noNoneStore each row's answer in reporting as a numeric scale derived from option order. Respondents still see a normal grid select. Cannot be combined with randomize_options.
number_secondsintno0Seconds to wait before allowing the respondent to continue
tagss.tag()no-Used for token substitution and grouping in reporting
idstrno-Optional stable identifier for this question
Example Code

Basic usage:

s.grid_select_question( "Please rate the following aspects of the product", row_name="Product Aspect", rows=["Quality", "Price", "Service", "Delivery"], options=["Poor", "Fair", "Good", "Very Good", "Excellent"] )

With recodes:

s.grid_select_question( "Rate the following aspects of the experience", row_name="Attribute", rows=["Cleanliness", "Friendliness", "Wait Time"], options=["Poor", "Fair", "Good", "Excellent"], recodes={ "Poor": "1", "Fair": "2", "Good": "3", "Excellent": "4" } )

With custom validator:

s.grid_select_question( "Rate the following aspects of {brand} cars", row_name="Attribute", rows=["Design", "Comfort", "Technology"], options=["Poor", "Fair", "Good", "Excellent"], brand="Tesla", custom_validator=lambda d: "Please vary your responses" if len(set(d.values())) == 1 else None )

Stored as a numeric scale for reporting:

s.grid_select_question( "How much do you agree with each statement?", row_name="Statement", rows=["The setup was easy", "Support was helpful", "I would recommend it"], options=["Strongly disagree", "Disagree", "Neutral", "Agree", "Strongly agree"], sorted="ascending" )
Notes
  • Set style="button" for a more compact tap-friendly interface
  • Use recodes to make scale responses easier to analyze
  • The custom_validator helps enforce variation or specific logic
  • specify_option enables per-row free text for non-listed choices
  • sorted is useful when the options form an ordered scale and you want reporting to treat each row's answer as a number. With sorted="ascending" the first option is stored as 1, the second as 2, and so on; sorted="descending" reverses this, storing the first option as the highest value and the last as 1. Respondents still pick from the option labels. Because the values come from the option order, sorted cannot be used with randomize_options (row order can still be randomized with randomize).