In it's simplest form, the Option List is a flat, selectable item list. With multi-selection, a Checkbox is incorporated.

Overview

Simple List

span: 6
---
const Example01 = () => {
  const flatData = [
    {
      text: "Atlanta",
      value: 1,
    },
    {
      text: "Boston",
      value: 2,
    },
    {
      text: "Charlotte",
      value: 3,
    },
    {
      text: "Detroit",
      value: 4,
    },
    {
      text: "El Paso",
      value: 5,
    }
  ]

  const [value, setValue] = React.useState([])
  const onChange = (data) => {
    setValue([data])
  }

  return <OptionList options={flatData} value={value} onChange={onChange} />
}

render (Example01)
span: 6
---
const Example01 = () => {
  const flatData = [
    {
      text: "Fort Lauderdale",
      value: 1,
    },
    {
      text: "Glendale",
      value: 2,
    },
    {
      text: "Honolulu",
      value: 3,
    },
    {
      text: "Indianapolis",
    value: 4,
    },
    {
      text: "Jacksonville",
      value: 5,
    }
  ]

  const [value, setValue] = React.useState([])
  const onChange = (data, checked) => {
    if (checked) setValue((prevState) => [...prevState, data]);
    else
      setValue((prevState) =>
        [...prevState].filter((item) => item !== data)
      );
  };

  return <OptionList options={flatData} value={value} onChange={onChange} multiple />
}

render (Example01)

Custom Content

Option Lists can have custom content inside individual options, regardless of what structure the Option List takes.

---
const Example01 = () => {
  const flatData = [
    {
      text: "Jane Doe",
      content: (
        <Stack alignItems="center" spacing={1}>
          <Avatar name="John Doe" autoColor size="s" />
          <BodyText size="small">Jane Doe</BodyText>
        </Stack>
      ),
      value: 1,
    },
    {
      text: "Dana Green",
      content: (
        <Stack alignItems="center" spacing={1}>
          <Avatar name="Dana Green" autoColor size="s" />
          <BodyText size="small">Dana Green</BodyText>
        </Stack>
      ),
      value: 2,
    },
    {
      text: "George Johnson",
      content: (
        <Stack alignItems="center" spacing={1}>
          <Avatar name="George Johnson" autoColor size="s" />
          <BodyText size="small">George Johnson</BodyText>
        </Stack>
      ),
      value: 3,
    }
  ]

  const [value, setValue] = React.useState([])
  const onChange = (data) => {
    setValue([data])
  }

  return <OptionList options={flatData} value={value} onChange={onChange} />
}

render (Example01)
---
const Example01 = () => {
const dummyFolderData = [
    {
        text: 'Jane Doe',
        content: (
        <Stack alignItems="center" spacing={1}>
          <Avatar name="Dana Green" autoColor size="s" />
          <BodyText size="small">Dana Green</BodyText>
        </Stack>
        ),
        value: 'jd',
        options: [
            {
                text: 'Irrigation 03/03/20',
                get content() {
                    return (
            <Stack alignItems="center">
              <BodyText size="small">
                {this.text}
              </BodyText>
              <Button size="xsmall" fill="subtle" primary iconName="chevron_right" onClick={() => alert("New page about this specific item")} className="m-l-half" />
            </Stack>
                    );
                },
                value: 54,
            },
            {
                text: 'Irrigation 03/04/20',
                get content() {
                    return (
            <Stack alignItems="center">
              <BodyText size="small">
                {this.text}
              </BodyText>
              <Button size="xsmall" fill="subtle" primary iconName="chevron_right" onClick={() => alert("New page about this specific item")} className="m-l-half" />
            </Stack>
                    );
                },
                value: 5444,
            }
        ],
    },
];

    const [value, setValue] = React.useState([]);
    const [collapseValue, setCollapseValue] = React.useState(['jd']);

    const onChange = (data, checked, children) => {
        const getValue = [data];
        const getChildrenValues = (options) => {
            options.map((option) => {
                if (option.options) getChildrenValues(option.options);
                if (option.disabled) return null;
                return getValue.push(option.value);
            });
        };

        if (children.length > 0) getChildrenValues(children);
        if (checked) setValue((prevState) => [...prevState, ...getValue]);
        else
            setValue((prevState) =>
                [...prevState].filter((item) => !getValue.includes(item))
            );
    };

    const onExpand = (data) => {
        collapseValue.includes(data)
            ? setCollapseValue((prevState) =>
                    [...prevState].filter((item) => item !== data)
              )
            : setCollapseValue((prevState) => [...prevState, data]);
    };

    return (
            <OptionList
                options={dummyFolderData}
                onChange={onChange}
                onExpand={onExpand}
                value={value}
                collapseValue={collapseValue}
        multiple
            />
  );
}

render (Example01)

Flat Groups

The Option List can be paired with an Eyebrow to categorize a simple item list.

span: 6
---
const Example01 = () => {
  const flatData = [
    {
      text: "Florida",
      value: 1,
      options: [{
        text: "Miami",
        value: 11,
      },
      {
        text: "Orlando",
        value: 12,
      },{
      text: "Tampa",
      value: 13}]
    },
    {
      text: "Minnesota",
      value: 2,
      options: [
        {
          text: "Minneapolis",
          value: 2,
        },
        {
          text: "Rochester",
          value: 3,
        },
        {
          text: "Saint Paul",
          value: 4,
        }
      ]}
  ]

  const [value, setValue] = React.useState([])
  const onChange = (data) => {
    setValue([data])
  }

  return <OptionList options={flatData} value={value} onChange={onChange} flatGroup />
}

render (Example01)
span: 6
---
const Example01 = () => {
  const flatData = [
    {
      text: "Montana",
      value: 1,
      options: [{
        text: "Billings",
        value: 11,
      },
      {
        text: "Missoula",
        value: 12,
      },{
      text: "Helena",
      value: 13}]
    },
    {
      text: "Colorado",
      value: 2,
      options: [
        {
          text: "Denver",
          value: 2,
        },
        {
          text: "Fort Collins",
          value: 3,
        },
        {
          text: "Boulder",
          value: 4,
        }
      ]}
  ]

  const [value, setValue] = React.useState([])
  const onChange = (data, checked) => {
    if (checked) setValue((prevState) => [...prevState, data]);
    else
      setValue((prevState) =>
        [...prevState].filter((item) => item !== data)
      );
  };

  return <OptionList options={flatData} value={value} onChange={onChange} flatGroup multiple />
}

render (Example01)

Group Selection

In a flat group, individual groups can be given a select all and none control.

---
const Example01 = () => {
  const flatData = [
    {
      text: "HVAC Install",
      value: 1,
      options: [{
        text: "Jane Doe",
        value: 11,
      },
      {
        text: "Jackie Robinson",
        value: 12,
      },{
      text: "Joseph Garcia",
      value: 13}]
    },
    {
      text: "Plumbing Replacement",
      value: 2,
      options: [
        {
          text: "Dana Green",
          value: 2,
        },
        {
          text: "Isabella Martinez",
          value: 3,
        },
        {
          text: "George Johnson",
          value: 4,
        }
      ]}
  ]

    const [value, setValue] = React.useState([]);

    const onChange = (data, checked) => {
        if (checked) setValue((prevState) => [...prevState, data]);
        else
            setValue((prevState) =>
                [...prevState].filter((item) => item !== data)
            );
    };

    const onGroupSelectAll = (data) => {
        const values = [...data.map((item) => item.value)];
        setValue((prevstate) => [...prevstate, ...values]);
    };

    const onGroupSelectNone = (data) => {
        const values = [...data.map((item) => item.value)];
        setValue((prevState) =>
            [...prevState].filter((item) => !values.includes(item))
        );
    };

  return <OptionList options={flatData} value={value} onChange={onChange} flatGroup={{ onGroupSelectAll, onGroupSelectNone }} multiple />
}

render (Example01)

Secondary Actions

A secondary action can be performed on individual options. Example actions include editing, deleting, or navigating to more information. Secondary actions are only visible on hover.

span: 12
---
const Example01 = () => {
  const flatData = [
    {
      text: "Raleigh",
      value: 1,
      secondaryAction: (<Button xsmall primary fill="subtle" onClick={() => alert("Action Clicked")}>Action</Button>)
    },
    {
      text: "Sacramento",
      value: 2,
      secondaryAction: (<Button xsmall primary fill="subtle" onClick={() => alert("Action Clicked")}>Action</Button>)
    },
    {
      text: "Tampa",
      value: 3,
      secondaryAction: (<Button xsmall primary fill="subtle" onClick={() => alert("Action Clicked")}>Action</Button>)
    }
  ]

  const [value, setValue] = React.useState([])
  const onChange = (data) => {
    setValue([data])
  }

  return <OptionList options={flatData} value={value} onChange={onChange} />
}

render (Example01)

Tree View

The tree view is a major variation of the Option List. It is used to represent hierarchical data, as understood by users. For example, an organizational chart, divisions within business units, or categorization of inventory items. Tree views work best when users perceive items as categorical.

span: 6
---
const Example01 = () => {
const dummyData = [
    {
        text: 'Plumbing',
    value: 1,
    collapsed: false,
        options: [
            {
                text: 'Install Materials',
                value: 11,
                options: [
                    {
                        text: 'Disposer Waste',
                        value: 111,
                    },
                    {
                        text: 'Frostproof Hydrant',
                        value: 112,
          }
                ],
            },
            {
                text: 'Service Materials',
        value: 12,
                options: [
                    {
                        text: 'Fluidmaster Fill Valve',
                        value: 121,
                    },
                    {
                        text: 'Tailpiece Slip Joint',
                        value: 122,
          }
                ],
            },
        ],
    }
];

    const [value, setValue] = React.useState([]);
    const [collapseValue, setCollapseValue] = React.useState([1, 11, 12]);

    const onChange = (data) => {
        setValue([data]);
    };

    const onExpand = (data) => {
        collapseValue.includes(data)
            ? setCollapseValue((prevState) =>
                    [...prevState].filter((item) => item !== data)
              )
            : setCollapseValue((prevState) => [...prevState, data]);
    };

  return <OptionList options={dummyData} value={value} collapseValue={collapseValue} onChange={onChange} onExpand={onExpand} />
}

render (Example01)
span: 6
---
const Example01 = () => {
const dummyData = [
    {
    text: 'Plumbing',
    value: 1,
    collapsed: false,
        options: [
            {
                text: 'Install Materials',
                value: 11,
                options: [
                    {
                        text: 'Disposer Waste',
                        value: 111,
                    },
                    {
                        text: 'Frostproof Hydrant',
                        value: 112,
          }
                ],
            },
            {
                text: 'Service Materials',
        value: 12,
                options: [
                    {
                        text: 'Fluidmaster Fill Valve',
                        value: 121,
                    },
                    {
                        text: 'Tailpiece Slip Joint',
                        value: 122,
          }
                ],
            },
        ],
    }
];

    const [value, setValue] = React.useState([]);
    const [collapseValue, setCollapseValue] = React.useState([1, 11, 12]);


    const onChange = (data, checked, children) => {
        const getValue = [data];
        const getChildrenValues = (options) => {
            options.map((option) => {
                if (option.options) getChildrenValues(option.options);
                if (option.disabled) return null;
                return getValue.push(option.value);
            });
        };

        if (children.length > 0) getChildrenValues(children);
        if (checked) setValue((prevState) => [...prevState, ...getValue]);
        else
            setValue((prevState) =>
                [...prevState].filter((item) => !getValue.includes(item))
            );
    };

    const onExpand = (data) => {
        collapseValue.includes(data)
            ? setCollapseValue((prevState) =>
                    [...prevState].filter((item) => item !== data)
              )
            : setCollapseValue((prevState) => [...prevState, data]);
  };

  return <OptionList options={dummyData} value={value} collapseValue={collapseValue} onChange={onChange} onExpand={onExpand} multiple />
}

render (Example01)

Tree Selection Logic

The Option List is not opinionated on what is selected when a parent of options is selected. Different variants can be used depending on the design context.

Selecting parent selects all children

When a parent is just the sum of all its children, selecting the parent can select all children. This is the most common

span: 6
---
const Example01 = () => {
const dummyData = [
    {
    text: 'Option',
    value: 1,
    collapsed: false,
        options: [
            {
                text: 'Sub Option',
                value: 11,
                options: [
                    {
                        text: 'Sub Sub Option',
                        value: 111,
                    },
                    {
                        text: 'Sub Sub Option',
                        value: 112,
                    }
                ],
            },
            {
                text: 'Sub Option',
                value: 12,
                options: [
                    {
                        text: 'Sub Sub Option',
                        value: 121,
                    },
                    {
                        text: 'Sub Sub Option',
                        value: 122,
          }
                ],
            },
        ]
    },
    {
    text: 'Option',
    value: 2,
        options: [
            {
                text: 'Sub Option',
                value: 21
            }
        ]
    }
];

    const [value, setValue] = React.useState([]);
    const [collapseValue, setCollapseValue] = React.useState([1, 11, 12]);


    const onChange = (data, checked, children) => {
        const getValue = [data];
        const getChildrenValues = (options) => {
            options.map((option) => {
                if (option.options) getChildrenValues(option.options);
                if (option.disabled) return null;
                return getValue.push(option.value);
            });
        };

        if (children.length > 0) getChildrenValues(children);
        if (checked) setValue((prevState) => [...prevState, ...getValue]);
        else
            setValue((prevState) =>
                [...prevState].filter((item) => !getValue.includes(item))
            );
    };

    const onExpand = (data) => {
        collapseValue.includes(data)
            ? setCollapseValue((prevState) =>
                    [...prevState].filter((item) => item !== data)
              )
            : setCollapseValue((prevState) => [...prevState, data]);
  };

  return <OptionList options={dummyData} value={value} collapseValue={collapseValue} onChange={onChange} onExpand={onExpand} multiple />
}

render (Example01)
span: 6
---
const Example01 = () => {
const dummyData = [
    {
    text: 'Option',
    value: 1,
    collapsed: false,
        options: [
            {
                text: 'Sub Option',
                value: 11,
                options: [
                    {
                        text: 'Sub Sub Option',
                        value: 111,
                    },
                    {
                        text: 'Sub Sub Option',
                        value: 112,
                    }
                ],
            },
            {
                text: 'Sub Option',
                value: 12,
                options: [
                    {
                        text: 'Sub Sub Option',
                        value: 121,
                    },
                    {
                        text: 'Sub Sub Option',
                        value: 122,
          }
                ],
            },
        ]
    },
    {
    text: 'Option',
    value: 2,
        options: [
            {
                text: 'Sub Option',
                value: 21
            }
        ]
    }
];

    const [value, setValue] = React.useState([]);
    const [collapseValue, setCollapseValue] = React.useState([1, 11, 12]);


    const onChange = (data, checked, children) => {
        const getValue = [data];
        const getChildrenValues = (options) => {
            options.map((option) => {
                if (option.options) getChildrenValues(option.options);
                if (option.disabled) return null;
                return getValue.push(option.value);
            });
        };

        if (children.length > 0) getChildrenValues(children);
        setValue((prevState) => [...getValue]);
    };

    const onExpand = (data) => {
        collapseValue.includes(data)
            ? setCollapseValue((prevState) =>
                    [...prevState].filter((item) => item !== data)
              )
            : setCollapseValue((prevState) => [...prevState, data]);
  };

  return <OptionList options={dummyData} value={value} collapseValue={collapseValue} onChange={onChange} onExpand={onExpand} />
}

render (Example01)

Selecting parent does not select children

There are times when a parent and child are independent selections from each other. This is useful when parent items can be configured distinctly from its children.

span: 6
---
const Example01 = () => {
const dummyData = [
    {
    text: 'Option',
    value: 1,
    collapsed: false,
        options: [
            {
                text: 'Sub Option',
                value: 11,
                options: [
                    {
                        text: 'Sub Sub Option',
                        value: 111,
                    },
                    {
                        text: 'Sub Sub Option',
                        value: 112,
                    }
                ],
            },
            {
                text: 'Sub Option',
                value: 12,
                options: [
                    {
                        text: 'Sub Sub Option',
                        value: 121,
                    },
                    {
                        text: 'Sub Sub Option',
                        value: 122,
          }
                ],
            },
        ]
    },
    {
    text: 'Option',
    value: 2,
        options: [
            {
                text: 'Sub Option',
                value: 21
            }
        ]
    }
];

    const [value, setValue] = React.useState([]);
    const [collapseValue, setCollapseValue] = React.useState([1, 11, 12]);

    const onChange = (data, checked) => {
        if (checked) setValue((prevState) => [...prevState, data]);
        else
            setValue((prevState) =>
                [...prevState].filter((item) => item !== data)
            );
    };

    const onExpand = (data) => {
        collapseValue.includes(data)
            ? setCollapseValue((prevState) =>
                    [...prevState].filter((item) => item !== data)
              )
            : setCollapseValue((prevState) => [...prevState, data]);
  };

  return <OptionList options={dummyData} value={value} collapseValue={collapseValue} onChange={onChange} onExpand={onExpand} multiple />
}

render (Example01)
span: 6
---
const Example01 = () => {
const dummyData = [
    {
    text: 'Option',
    value: 1,
    collapsed: false,
        options: [
            {
                text: 'Sub Option',
                value: 11,
                options: [
                    {
                        text: 'Sub Sub Option',
                        value: 111,
                    },
                    {
                        text: 'Sub Sub Option',
                        value: 112,
                    }
                ],
            },
            {
                text: 'Sub Option',
                value: 12,
                options: [
                    {
                        text: 'Sub Sub Option',
                        value: 121,
                    },
                    {
                        text: 'Sub Sub Option',
                        value: 122,
          }
                ],
            },
        ]
    },
    {
    text: 'Option',
    value: 2,
        options: [
            {
                text: 'Sub Option',
                value: 21
            }
        ]
    }
];

    const [value, setValue] = React.useState([]);
    const [collapseValue, setCollapseValue] = React.useState([1, 11, 12]);

    const onChange = (data) => {
        setValue([data])
    };

    const onExpand = (data) => {
        collapseValue.includes(data)
            ? setCollapseValue((prevState) =>
                    [...prevState].filter((item) => item !== data)
              )
            : setCollapseValue((prevState) => [...prevState, data]);
  };

  return <OptionList options={dummyData} value={value} collapseValue={collapseValue} onChange={onChange} onExpand={onExpand} />
}

render (Example01)

Non-selectable Parents

Sometimes only childless options should be selected. For example, if parents represent folder structures, but the user needs to select a file.

span: 12
---
const Example01 = () => {
const dummyFolderData = [
    {
        text: 'Pictures',
        content: (
            <Stack alignItems="center" spacing={1}>
                <Icon
                    className="m-l-half c-neutral-100"
                    name="folder"
                    size="16px"
                />
                <BodyText size="small">Pictures</BodyText>
            </Stack>
        ),
        value: 'pictures',
        readOnly: true,
        options: [
            {
                text: 'dog.png',
                get content() {
                    return (
                        <BodyText size="small" className="m-l-1">
                            {this.text}
                        </BodyText>
                    );
                },
                value: 54,
            },
            {
                text: 'cat.jpg',
                get content() {
                    return (
                        <BodyText size="small" className="m-l-1">
                            {this.text}
                        </BodyText>
                    );
                },
                value: 5444,
            }
        ],
    },

    {
        text: 'Documents',
        content: (
            <Stack alignItems="center" spacing={1}>
                <Icon
                    className="m-l-half c-neutral-100"
                    name="folder"
                    size="16px"
                />
                <BodyText size="small">Documents</BodyText>
            </Stack>
        ),
        value: 'documents',
        readOnly: true,
        options: [
            {
                text: 'worksheet.xls',
                get content() {
                    return (
                        <BodyText size="small" className="m-l-1">
                            {this.text}
                        </BodyText>
                    );
                },
                value: 541,
            },
            {
                text: 'README.md',
                get content() {
                    return (
                        <BodyText size="small" className="m-l-1">
                            {this.text}
                        </BodyText>
                    );
                },
                value: 54441,
            }
        ],
    },
];

    const [value, setValue] = React.useState([]);
    const [collapseValue, setCollapseValue] = React.useState([
        'pictures',
    ]);
    const onChange = (data) => setValue([data]);

    const onExpand = (data) => {
        collapseValue.includes(data)
            ? setCollapseValue((prevState) =>
                    [...prevState].filter((item) => item !== data)
              )
            : setCollapseValue((prevState) => [...prevState, data]);
    };

    return (
            <OptionList
                options={dummyFolderData}
                onChange={onChange}
                onExpand={onExpand}
                value={value}
                collapseValue={collapseValue}
            />
  );
}

render (Example01)

Tree View best practices

  • Minimize the number of nested levels needed to represent the data.
    • If the same data can be represented with 2 nested levels and 3, choose the 2 nested option.
  • Categories should represent how a user best perceives a hierarchy. There are many different ways to categorize objects (e.g. is a video game console a toy or an electronic), it is important to understand how end-users perceive it.

Caution when using a Tree View

  • Any usage of non-hierarchical data. Use a simpler Option List instead.
  • User generated Trees. This data structure works best when data follows some sort of parent-child relation. User created structure may cause disorder and clutter that actively impairs a users ability to handle the data.
  • When a child element might belong to multiple categories. This can create confusion for users in finding what they are looking for.
  • In general, flat lists are easier to parse through than Trees. Designers and product managers should consider whether the hierarchy’s representation improves a user’s ability to work.
  • For a simple list, particularly one tied to Forms, consider the Checkbox or Radio.
  • For a selection list that needs to be in an overlay, use the Select.
  • For a list of navigational items, consider using the Side Nav.