Matrix and Grid Questions in XLSForm (table-list)
To build a matrix (grid) question in XLSForm, wrap the questions in a group and write table-list in the appearance column of its begin_group row; every question inside must be a select_one or select_multiple of the same choice list. Each question becomes one row, and the choices become the column headings. For more control, use field-list on the group with label on a header row and list-nolabel on every question. Every example below compiles with pyxform and ODK Validate.
How do I make a matrix question with table-list?
A community survey asks how satisfied the household is with five public services, all on the same 4-point scale. As separate questions that is five screens with the same four answers. As a grid it is one screen:
| type | name | label | appearance | required |
|---|---|---|---|---|
| begin_group | services | How satisfied is the household with each of these services? | table-list | |
| select_one satisfaction | sat_water | Water supply | yes | |
| select_one satisfaction | sat_clinic | Health clinic | yes | |
| select_one satisfaction | sat_school | Primary school | yes | |
| select_one satisfaction | sat_road | Roads | yes | |
| select_one satisfaction | sat_power | Electricity | yes | |
| end_group | services |
| list_name | name | label |
|---|---|---|
| satisfaction | 1 | Very satisfied |
| satisfaction | 2 | Satisfied |
| satisfaction | 3 | Dissatisfied |
| satisfaction | 4 | Very dissatisfied |
- The group's label is shown above the grid, so put the question there and the item in each row.
- Each row is a normal question: it has its own name, its own column in the data and its own
required,relevantandconstraint. - Every row must use the same list. With two lists in one
table-listgroup, the form is rejected (see the mistakes table below).
table-list is a shortcut. When the form is converted, pyxform turns it into a field-list group, a note with the group's label, a header row with the label appearance and list-nolabel on each question. The note and the header row each add an empty column to the data (named generated_table_list_label_... and reserved_name_for_field_list_labels_...); you can drop them at analysis time.
How do I build the grid by hand with label and list-nolabel?
Building the grid yourself gives you what table-list cannot: a note or instruction above the grid, and a header row repeated partway down a long table so the enumerator never loses track of the columns. A child feeding module asking about 24-hour food groups:
| type | name | label | appearance | required |
|---|---|---|---|---|
| begin_group | food_groups | Food groups eaten yesterday | field-list | |
| note | fg_intro | Ask about everything the child ate or drank yesterday, during the day or night. | ||
| select_one yes_no_dk | fg_header_1 | Food group | label | |
| select_one yes_no_dk | fg_grains | Grains, roots and tubers | list-nolabel | yes |
| select_one yes_no_dk | fg_pulses | Beans, peas, lentils and nuts | list-nolabel | yes |
| select_one yes_no_dk | fg_dairy | Milk, yoghurt and cheese | list-nolabel | yes |
| select_one yes_no_dk | fg_meat | Meat, fish and poultry | list-nolabel | yes |
| select_one yes_no_dk | fg_header_2 | Food group | label | |
| select_one yes_no_dk | fg_eggs | Eggs | list-nolabel | yes |
| select_one yes_no_dk | fg_vita | Vitamin A rich fruits and vegetables | list-nolabel | yes |
| select_one yes_no_dk | fg_other_fv | Other fruits and vegetables | list-nolabel | yes |
| end_group | food_groups |
| list_name | name | label |
|---|---|---|
| yes_no_dk | 1 | Yes |
| yes_no_dk | 0 | No |
| yes_no_dk | -99 | Don't know |
field-listputs the whole group on one screen. Without it, each row is its own screen and there is no grid.labelshows only the choice labels, with no buttons: that is the header row. Its question label ("Food group") sits in the first column.list-nolabelshows only the buttons, lined up under the header.- Header rows are questions too, so each needs its own name (
fg_header_1,fg_header_2). Leave them withoutrequired: nobody can answer them.
ODK also has a list appearance, which shows the choice labels next to the buttons on every row. Use it instead of a header row when the choice labels are long and the columns would not line up.
Can a matrix use select_multiple?
Yes. A row of checkboxes works the same way, with each question as a row. A food security survey asking which meals each group in the household skipped:
| type | name | label | appearance |
|---|---|---|---|
| begin_group | meals_skipped | Which meals did each of these people skip yesterday? | table-list |
| select_multiple meal | skip_child | Children under 5 | |
| select_multiple meal | skip_women | Adult women | |
| select_multiple meal | skip_men | Adult men | |
| end_group | meals_skipped |
| list_name | name | label |
|---|---|---|
| meal | 1 | Breakfast |
| meal | 2 | Lunch |
| meal | 3 | Dinner |
Each row is saved as a space-separated list of codes, such as 1 3. To make "None" exclusive in a row, add a constraint to that row; the none of the above guide has the expression.
How do I add skip logic inside a matrix?
relevant works on each row and on the whole group. A row that is not relevant disappears from the grid; a group that is not relevant hides the whole grid:
| type | name | label | appearance | relevant | required |
|---|---|---|---|---|---|
| integer | school_children | How many children in the household go to school? | yes | ||
| select_one yes_no | uses_clinic | Has anyone in the household used the health clinic in the last 12 months? | yes | ||
| begin_group | school_rating | How would you rate the school on each of these? | table-list | ${school_children} > 0 | |
| select_one rating | sch_teaching | Quality of teaching | yes | ||
| select_one rating | sch_toilets | Toilets | yes | ||
| select_one rating | sch_meals | School meals | yes | ||
| select_one rating | sch_clinic_link | Referrals to the health clinic | ${uses_clinic} = 1 | yes | |
| end_group | school_rating |
| list_name | name | label |
|---|---|---|
| rating | 1 | Good |
| rating | 2 | Fair |
| rating | 3 | Poor |
| list_name | name | label |
|---|---|---|
| yes_no | 1 | Yes |
| yes_no | 0 | No |
Put the questions that decide the logic before the grid, on their own screen. In a field-list, rows react as soon as an earlier question on the same screen is answered, which can make the grid jump while the enumerator is filling it in. For the expressions themselves, see the XLSForm skip logic guide.
A grid shows all its rows at once, so required only complains when the enumerator tries to leave the screen. Mark every row required (as above) or a missed row is easy to swipe past.
Can a matrix have number or text boxes?
Not in ODK. table-list, label and list-nolabel only line up select questions; there is no grid of number or text inputs in ODK Collect, and that is a long-standing request on the ODK forum. The closest is a field-list group of short-labelled questions, all on one screen. A livestock count:
| type | name | label | appearance | constraint | constraint_message |
|---|---|---|---|---|---|
| begin_group | livestock | How many of these animals does the household own now? | field-list | ||
| integer | n_cattle | Cattle | . >= 0 and . <= 500 | Enter a number from 0 to 500 | |
| integer | n_goats | Goats and sheep | . >= 0 and . <= 500 | Enter a number from 0 to 500 | |
| integer | n_poultry | Chickens and ducks | . >= 0 and . <= 2000 | Enter a number from 0 to 2000 | |
| end_group | livestock |
For the same number of things measured several times (crops by area, harvest and sales), a repeat with one round per item is usually cleaner; see XLSForm repeat groups.
KoboToolbox's form builder has its own "Question Matrix" type, which can hold number and text columns. It is saved in the XLSForm as begin_kobomatrix with a kobo--matrix_list column, works only on KoboToolbox, and the form will not convert with standard pyxform or load in ODK Central.
How do I keep a matrix readable on a phone?
A grid that looks fine in Excel can be unreadable on a 6-inch screen. The ODK forum has years of threads about headers that do not line up with the buttons. What works in the field:
- Four or five columns at most. A 5-point scale plus "Don't know" is already tight on a phone.
- Short choice labels. "Very satisfied" fits; "Somewhat satisfied with the service" wraps and pushes the columns out of line. Put the full wording in the group label if needed.
- About eight rows per grid. Split a 20-item battery into three groups, or repeat the header row as in the hand-built example.
- Test on the actual devices. Tablets show grids well; small phones may need landscape mode.
Grids are fast but they invite straight-lining: the same answer down the whole column. For attitude scales where that matters, consider asking the items one at a time, or randomizing their order (see randomize choices and question order).
Common matrix mistakes and how to fix them
These are the messages pyxform and ODK Validate return, which is what ODK Central, KoboToolbox or the XLSForm converter shows when it rejects the form:
| You wrote | Write instead | Error you will see |
|---|---|---|
select_one satisfaction and select_one yes_no in one table-list group | one list for every row, or two groups | Badly formatted table list, list names don't match: satisfaction vs. yes_no |
begin_group with table-list but no end_group | close the group with end_group | Unmatched 'begin_group'. No matching 'end_group' was found for the name 'services'. |
table-list in the appearance of a question | put it on the begin_group row | No error, but no grid: the question shows on its own |
label and list-nolabel rows in a group without field-list | field-list on the begin_group row | No error, but each row is a separate screen |
list-nolabel rows with no label header row | add a header question with the label appearance | No error, but the buttons have no column headings |
A text or integer question in a table-list group | move it out of the group, or use a field-list group | No error, but it is not a grid row |
For other messages, see common XLSForm errors or paste the form into the free XLSForm validator.
Does this work the same in ODK, KoboToolbox and SurveyCTO?
The XLSForm syntax is the same on all three; what differs is how each app draws the grid:
| Platform | table-list on a group | field-list + label + list-nolabel | Number or text grid |
|---|---|---|---|
| ODK Collect | Yes | Yes | No |
| KoboToolbox (XLSForm upload) | Yes, same syntax | Yes | Only with Kobo's own Question Matrix (begin_kobomatrix) |
| SurveyCTO | See SurveyCTO's documentation | Yes, field-list group with label and list-nolabel | No |
Web forms (Enketo, used by KoboToolbox and ODK Central) draw grids differently from the Android app, and the header row can wrap differently. Preview the form on the web and on a phone before fieldwork.
When a grid is the right choice
Use a grid when several items share one short answer scale and the enumerator reads them in a row: service satisfaction, yes/no food groups, symptom checklists, asset ownership. Keep separate questions when the items need different answer lists, follow-up questions or long explanations; a grid hides those details. For the other ways to change how questions look, see the XLSForm appearance column.
You can describe the grid in plain language instead: "ask satisfaction with water, clinic, school, roads and electricity on a 4-point scale, as one table". LooprAI writes the group, the appearance and the choice list, then compiles the form with pyxform before anything is saved.
Next: XLSForm appearance column →
Frequently Asked Questions
How do I create a matrix question in XLSForm?
Put the questions in a group and write table-list in the appearance column of the begin_group row. Every question inside must be a select_one or select_multiple of the same choice list. Each question becomes a row and the choices become the column headings.
What is the difference between table-list and field-list?
field-list shows every question in a group on one screen. table-list does that and also lines the questions up as a grid with one header row. pyxform converts table-list into a field-list group with a label header row and list-nolabel on each question.
Why do I get 'Badly formatted table list, list names don't match'?
The questions in a table-list group use different choice lists. A grid has one set of columns, so every row must use the same list. Change the rows to one list, or split them into two groups with their own table-list.
Can I put integer or text questions in an ODK matrix?
No. ODK lines up only select questions in a grid. For numbers, use a field-list group of short-labelled integer questions on one screen, or a repeat with one round per item. KoboToolbox's own Question Matrix allows number columns but works only on KoboToolbox.
How do I repeat the header row in a long matrix?
Build the grid by hand: field-list on the group, then a select_one with the label appearance wherever you want a header, and list-nolabel on each question row. Give every header row its own name and leave it optional.
Why are the matrix headings not lined up with the buttons?
Long choice labels or too many columns for the screen width. Shorten the choice labels, keep to four or five columns, try landscape mode, or use the list appearance so each row shows its own labels instead of a shared header.
No Credit Card Required
Build the form first. Add hosting when you need it.
Start free with LooprAI, then deploy a managed ODK Central server or run DataSnap checks when your project is ready.
Automation handles the mechanical work.
Researchers decide what the evidence means.