suiss/uim · React Native Component

Questionnaire

A step-by-step form that shows one question at a time, with choices, text answers, validation, skipping and a submit step.

Questionnaire is an open-source mobile component from suiss/uim for React Native and Expo, with SwiftUI and Jetpack Compose versions built from the same design tokens. The @esuiss/uim CLI copies its source into your project, so you own the code.

iOS
Questionnaire on iOS in light mode

Questionnaire is a View that tracks which question is active, collects answers and validates them. Each QuestionnaireItem is one question; only the active item is shown, and moving between items slides the content horizontally with a Reanimated keyframe that follows the layout direction.

Items hold QuestionnaireChoice rows, which are Pressable radios or, with multiple, checkboxes, or a QuestionnaireInput for a typed answer. QuestionnaireActions holds the Previous, Skip, Next and Submit buttons, each of which only appears when it applies.

Installation

$ pnpm dlx @esuiss/uim@latest add questionnaire

The CLI also adds what it builds on: Button, Icon, Input, Text.

Manual

  1. Install the dependencies:
npx expo install cn phosphor-react-native react-native-reanimated react-native-svg react-native-worklets
  1. Add the pieces it builds on: Button, Icon, Input, Text, and the primitives components/primitives/haptics.tsx, components/primitives/hooks.tsx, components/primitives/use-render.tsx.

  2. Copy the source into components/ui/questionnaire.tsx:

uniwind/components/ui/questionnaire.tsx
import { cn } from "cn"
import { CheckIcon } from "phosphor-react-native"
import * as React from "react"
import {
  type GestureResponderEvent,
  I18nManager,
  Pressable,
  View,
} from "react-native"
import Animated, { Keyframe, ReduceMotion } from "react-native-reanimated"
import { useHaptics } from "@/components/primitives/haptics"
import { useEffectEvent } from "@/components/primitives/hooks"
import { resolveClassName } from "@/components/primitives/use-render"
import { Button, type ButtonProps } from "@/components/ui/button"
import { Icon } from "@/components/ui/icon"
import { Input } from "@/components/ui/input"
  1. Update the import paths to match your project.

Usage

import { Questionnaire, QuestionnaireProgress, QuestionnaireItem, QuestionnaireTitle, QuestionnaireDescription, QuestionnaireChoices, QuestionnaireChoice, QuestionnaireChoiceDescription, QuestionnaireInput, QuestionnaireError, QuestionnaireActions, QuestionnairePrevious, QuestionnaireSkip, QuestionnaireNext, QuestionnaireSubmit } from "@/components/ui/questionnaire"
<Onboarding />

Give every QuestionnaireItem a unique name; answers are collected under that name. Mark items required to block moving on without an answer; optional items show the Skip button. QuestionnaireError appears only when the item is invalid and has a default message you can replace.

The active item is uncontrolled through defaultItem or controlled with item and onItemChange. onSubmit receives the collected values: a string for single-answer items and a string array for multiple items.

QuestionnaireChoice supports defaultChecked, or controlled checked with onCheckedChange. QuestionnaireInput takes a type that sets the keyboard, autocomplete and secure entry. QuestionnaireProgress shows "Question 1 of 3" by default and accepts a render function. Use useQuestionnaireContext to build custom controls, or useQuestionnaire for the underlying state.

Examples

Mid flow

Starts on the second question with defaultItem.

iOS
Questionnaire (mid flow) on iOS in light mode

Invalid

A required question shown in its invalid state with an error message.

iOS
Questionnaire (invalid) on iOS in light mode

Multiple choice

A multiple item that accepts several checked choices.

iOS
Questionnaire (multiple choice) on iOS in light mode

With choice descriptions

Choices with secondary text from QuestionnaireChoiceDescription.

iOS
Questionnaire (with choice descriptions) on iOS in light mode

Text input

A required question answered with QuestionnaireInput.

iOS
Questionnaire (text input) on iOS in light mode

API Reference

Questionnaire

Root that holds the state and the active question.

ExtendsReact.ComponentProps<typeof View>React.RefAttributes<View>UseQuestionnaireParameters

QuestionnaireProgress

Shows the current question number and total.

ExtendsReact.ComponentProps<typeof View>React.RefAttributes<View>
PropType
Type
React.ReactNode((state: QuestionnaireState) => React.ReactNode)
Default
—

QuestionnaireItem

One question; shown only while active.

ExtendsReact.ComponentProps<typeof View>React.RefAttributes<View>
PropType
About
Unique key for the answer.
Type
string
Default
required
About
Requires an answer before moving on and hides Skip.
Type
boolean
Default
false
About
Removes the item from the sequence.
Type
boolean
Default
false
About
Forces the invalid state.
Type
boolean
Default
false
About
Allows more than one choice.
Type
boolean
Default
false
About
Called when the status changes to unanswered, answered or skipped.
Type
(status: QuestionnaireItemStatus) => void
Default
—

QuestionnaireTitle

The question heading.

ExtendsReact.ComponentProps<typeof Text>React.RefAttributes<typeof Text>

QuestionnaireDescription

Secondary text under the title.

ExtendsReact.ComponentProps<typeof Text>React.RefAttributes<typeof Text>

QuestionnaireChoices

Stack of choice rows.

ExtendsReact.ComponentProps<typeof View>React.RefAttributes<View>

QuestionnaireChoice

A selectable answer row.

ExtendsReact.ComponentProps<typeof Pressable>React.RefAttributes<View>
PropType
About
Value stored when the choice is selected.
Type
string
Default
required
About
Controlled checked state.
Type
boolean
Default
—
About
Initial checked state.
Type
boolean
Default
false
About
Disables the choice.
Type
boolean
Default
false
About
Called with the next checked state.
Type
(checked: boolean) => void
Default
—
Type
React.ReactNode
Default
—

QuestionnaireChoiceDescription

Secondary text inside a choice.

ExtendsReact.ComponentProps<typeof Text>React.RefAttributes<typeof Text>

QuestionnaireInput

Text field for a typed answer.

ExtendsReact.ComponentProps<typeof Input>
PropType
About
text, email, number, tel, url, password or search; sets keyboard and autofill props.
Type
QuestionnaireInputType
Default
"text"

QuestionnaireError

Validation message shown when the item is invalid.

ExtendsReact.ComponentProps<typeof Text>React.RefAttributes<typeof Text>

QuestionnaireActions

Row for the navigation buttons.

ExtendsReact.ComponentProps<typeof View>React.RefAttributes<View>

QuestionnairePrevious

Goes to the previous question.

ExtendsQuestionnaireNavigationProps

QuestionnaireSkip

Skips the active question when it is optional.

ExtendsQuestionnaireNavigationProps

QuestionnaireNext

Validates and moves to the next question.

ExtendsQuestionnaireNavigationProps

QuestionnaireSubmit

Validates all questions and calls onSubmit on the last step.

ExtendsQuestionnaireNavigationProps

Accessibility

  • Each item has the radiogroup role, or group when multiple, and is labelled by its QuestionnaireTitle, which has the heading role.
  • Choices have the radio or checkbox role with checked and disabled states.
  • QuestionnaireInput is labelled by the title unless you pass an accessibilityLabel.
  • QuestionnaireProgress has the progressbar role, a value such as "Question 2 of 3" and a polite live region.
  • QuestionnaireError has the alert role with a polite live region.
  • Slide transitions respect the system reduce motion setting.

Notes

  • Selecting a choice plays a selection haptic.
  • Selected choices use border-accent and bg-accent; invalid items use border-danger.
  • The slide direction flips for right-to-left layouts.
  • Light and dark colors follow the system appearance.