# Custom Poll Flow

This cookbook shows how to customize a `Channel` screen with the [`Poll` component](/chat/docs/sdk/react-native/v8/ui-components/poll/) and use navigation instead of modals.

## Best Practices

- Enable polls at the dashboard level before debugging UI behavior.
- Keep poll screens inside `Channel` so they inherit the correct contexts.
- Reuse default poll subcomponents and override only the pieces you need.
- Route poll navigation explicitly to avoid mixing modal and stack flows.
- Handle poll state updates through provided hooks instead of manual mutations.

### Prerequisites

Prereq: a `Channel` screen with a `MessageList`. Polls are tied to messages, so this must be in place first.

Polls are disabled by default. Enable them in the Stream Dashboard by toggling the `Poll` button.

![Enable Polls](@chat-sdk/react-native/v8/_assets/guides/custom-poll-flow/enable-polls.png)

Doing that will include the `Poll` button in your attachment picker:

![Enable Polls](@chat-sdk/react-native/v8/_assets/guides/custom-poll-flow/attachment-picker-poll-button.png)

You also need a working `chatClient`. Examples use React Navigation, but any navigation library works.

### The initial UI

Without doing any form of customization yet, we should have something similar to the following:

```tsx
import {
  OverlayProvider,
  Chat,
  Channel,
  MessageList,
  MessageInput,
} from "stream-chat-react-native";

const ChannelScreen = () => {
  return (
    <OverlayProvider>
      <Chat client={client}>
        <Channel channel={channel}>
          <ChannelHeader />
          <MessageList />
          <MessageInput />
        </Channel>
      </Chat>
    </OverlayProvider>
  );
};
```

The poll creation flow should work out of the box with the default UI:

![Default UI 1](@chat-sdk/react-native/v8/_assets/guides/custom-poll-flow/default-ui-1.png)

![Default UI 2](@chat-sdk/react-native/v8/_assets/guides/custom-poll-flow/default-ui-2.png)

![Default UI 3](@chat-sdk/react-native/v8/_assets/guides/custom-poll-flow/default-ui-3.png)

Next, customize the poll UI inside messages by setting `PollContent` on `Channel`.

For now, remove the poll header and adjust the bottom buttons. Reuse the default `PollContent` and override parts.

You can reuse default buttons and override only what you need.

```tsx {7,10,11,12,13,14,15,16,17,18,19,20,21,22,28}
import {
  OverlayProvider,
  Chat,
  Channel,
  MessageList,
  MessageInput,
  PollContent,
} from "stream-chat-react-native";

const MyPollButtons = () => {
  return (
    <>
      <ShowAllOptionsButton />
      <ViewResultsButton
        onPress={({ message, poll }) =>
          Alert.alert(`Message ID: ${message.id} and Poll ID: ${poll.id}`)
        }
      />
      <EndVoteButton />
    </>
  );
};

const MyPollContent = () => (
  <PollContent PollHeader={() => null} PollButtons={MyPollButtons} />
);

const ChannelScreen = () => {
  return (
    <OverlayProvider>
      <Chat client={client}>
        <Channel channel={channel} PollContent={MyPollContent}>
          <ChannelHeader />
          <MessageList />
          <MessageInput />
        </Channel>
      </Chat>
    </OverlayProvider>
  );
};
```

![Step 1-1](@chat-sdk/react-native/v8/_assets/guides/custom-poll-flow/custom-step-1-1.png)

![Step 1-2](@chat-sdk/react-native/v8/_assets/guides/custom-poll-flow/custom-step-1-2.png)

Now only two buttons render (`View Results` and `End Vote`). `View Results` shows an `Alert` instead of opening the modal.

Next, reintroduce the `PollResults` screen using your own navigation instead of [React Native Modals](https://reactnative.dev/docs/modal).

To do this, let's rely on `React Navigation`. Since we need all of the `Poll` screens to also be children of the `Channel` component, we can introduce an additional `Stack` navigator that would take care of this.

```tsx {2,18,19,20,21,22,23,24,25,26,28,35,36,37,38,39,40,41,42}
import {
  OverlayProvider,
  Chat,
  Channel,
  MessageList,
  MessageInput,
  PollContent,
} from "stream-chat-react-native";
import { createStackNavigator } from "@react-navigation/stack";

const MyPollButtons = () => {
  return (
    <>
      <ShowAllOptionsButton />
      <ViewResultsButton
        onPress={({ message, poll }) =>
          Alert.alert(`Message ID: ${message.id} and Poll ID: ${poll.id}`)
        }
      />
      <EndVoteButton />
    </>
  );
};

const MyPollContent = () => (
  <PollContent PollHeader={() => null} PollButtons={MyPollButtons} />
);

const ChannelMessageList = () => {
  return (
    <>
      <ChannelHeader />
      <MessageList />
      <MessageInput />
    </>
  );
};

const ChannelStack = createStackNavigator<StackNavigatorParamList>();

const ChannelScreen = () => {
  return (
    <OverlayProvider>
      <Chat client={client}>
        <Channel channel={channel} PollContent={MyPollContent}>
          <ChannelStack.Navigator initialRouteName={"ChannelMessageList"}>
            <ChannelStack.Screen
              name={"ChannelMessageList"}
              options={{ headerShown: false }}
              component={ChannelMessageList}
            />
            <ChannelStack.Screen
              name={"PollResultsScreen"}
              options={{ headerShown: false }}
              component={() => null}
            />
          </ChannelStack.Navigator>
        </Channel>
      </Chat>
    </OverlayProvider>
  );
};
```

For now, leave `PollResultsScreen` empty. This stack inside `Channel` can now host all poll screens.

This keeps `channel` and other `Channel` customizations in one place while using your own navigation.

As a next step, let's reconstruct the `PollResults` screen. For this, we may use the `PollModalHeader` and `PollResults` components and get the default UI out of the box.

```tsx {8,9,32,33,34,35,36,37,38,39,40,41,42,43,44,60}
import {
  OverlayProvider,
  Chat,
  Channel,
  MessageList,
  MessageInput,
  PollContent,
  PollResults,
  PollModalHeader,
} from 'stream-chat-react-native';
import { createStackNavigator } from '@react-navigation/stack';

const MyPollButtons = () => {
  return (
    <>
      <ShowAllOptionsButton />
      <ViewResultsButton
        onPress={({ message, poll }) =>
          navigation.navigate('PollResultsScreen', {
            message,
            poll,
          });
        }
      />
      <EndVoteButton />
    </>
  )
}

// ... rest of the components

const PollResultsScreen = ({
  route: {
    params: { message, poll },
  },
}) => {
  const navigation = useNavigation();
  return (
    <SafeAreaView style={{ flex: 1 }}>
      <PollModalHeader title={'RESULTS'} onPress={() => navigation.goBack()} />
      <PollResults message={message} poll={poll} />
    </SafeAreaView>
  );
};

const ChannelScreen = () => {
  return (
    <OverlayProvider>
      <Chat client={client}>
        <Channel channel={channel} PollContent={MyPollContent}>
          <ChannelStack.Navigator initialRouteName={'ChannelMessageList'}>
            <ChannelStack.Screen
              name={'ChannelMessageList'}
              options={{ headerShown: false }}
              component={ChannelMessageList}
            />
            <ChannelStack.Screen
              name={'PollResultsScreen'}
              options={{ headerShown: false }}
              component={PollResultsScreen}
            />
          </ChannelStack.Navigator>
        </Channel>
      </Chat>
    </OverlayProvider>
  );
};
```

`View Results` now navigates to `PollResults` in the stack instead of a modal.

![Custom Results](@chat-sdk/react-native/v8/_assets/guides/custom-poll-flow/custom-results.png)

The UI matches the default, but the title is now `RESULTS`.

Next, pin the poll name at the top for long option lists.

For this purpose, we can use the `usePollState` hook and the `PollResultsContent` component.

```tsx {3,4,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31}
import {
  // ...rest of the imports
  usePollState,
  PollResultsContent,
} from "stream-chat-react-native";
import { createStackNavigator } from "@react-navigation/stack";

// ... rest of the components

const MyPollResultsContent = () => {
  const { name } = usePollState();
  const navigation = useNavigation();
  return (
    <>
      <PollModalHeader title={name} onPress={() => navigation.goBack()} />
      <PollResultsContent />
    </>
  );
};

const PollResultsScreen = ({
  route: {
    params: { message, poll },
  },
}) => {
  return (
    <SafeAreaView style={{ flex: 1 }}>
      <PollResults
        message={message}
        poll={poll}
        PollResultsContent={MyPollResultsContent}
      />
    </SafeAreaView>
  );
};

const ChannelScreen = () => {
  return (
    <OverlayProvider>
      <Chat client={client}>
        <Channel channel={channel} PollContent={MyPollContent}>
          <ChannelStack.Navigator initialRouteName={"ChannelMessageList"}>
            <ChannelStack.Screen
              name={"ChannelMessageList"}
              options={{ headerShown: false }}
              component={ChannelMessageList}
            />
            <ChannelStack.Screen
              name={"PollResultsScreen"}
              options={{ headerShown: false }}
              component={PollResultsScreen}
            />
          </ChannelStack.Navigator>
        </Channel>
      </Chat>
    </OverlayProvider>
  );
};
```

Providing us with the following UI:

![Custom Results Title](@chat-sdk/react-native/v8/_assets/guides/custom-poll-flow/custom-results-title.png)

This works because `PollResults` is wrapped in `PollContext`, and the button callback provides the data needed to initialize the provider.

Do the same for poll creation for consistency.

```tsx {3,9,10,11,12,13,14,15,16,17,18,19,20,23,30,43,44,45,46,47,48,49}
import {
  // ...rest of the imports
  CreatePoll,
} from "stream-chat-react-native";
import { createStackNavigator } from "@react-navigation/stack";

// ... rest of the components

const MyCreatePollContent = ({
  route: {
    params: { sendMessage },
  },
}) => {
  const navigation = useNavigation();
  return (
    <SafeAreaView style={{ flex: 1 }}>
      <CreatePoll
        sendMessage={sendMessage}
        closePollCreationDialog={() => navigation.goBack()}
      />
    </SafeAreaView>
  );
};

const ChannelScreen = () => {
  const navigation = useNavigation();
  return (
    <OverlayProvider>
      <Chat client={client}>
        <Channel
          channel={channel}
          PollContent={MyPollContent}
          openPollCreationDialog={({ sendMessage }) =>
            navigation.navigate("CreatePollScreen", { sendMessage })
          }
        >
          <ChannelStack.Navigator initialRouteName={"ChannelMessageList"}>
            <ChannelStack.Screen
              name={"ChannelMessageList"}
              options={{ headerShown: false }}
              component={ChannelMessageList}
            />
            <ChannelStack.Screen
              name={"PollResultsScreen"}
              options={{ headerShown: false }}
              component={PollResultsScreen}
            />
            <ChannelStack.Group screenOptions={{ presentation: "modal" }}>
              <ChannelStack.Screen
                name={"CreatePollScreen"}
                options={{ headerShown: false }}
                component={MyCreatePollContent}
              />
            </ChannelStack.Group>
          </ChannelStack.Navigator>
        </Channel>
      </Chat>
    </OverlayProvider>
  );
};
```

Final UI:

![Step 1-1](@chat-sdk/react-native/v8/_assets/guides/custom-poll-flow/custom-creation-1.png)

![Step 1-2](@chat-sdk/react-native/v8/_assets/guides/custom-poll-flow/custom-creation-2.png)

Going forward with customizations, let's change the background color of our `Poll`s. Since all `Poll` components are `theme` compatible, we can do this by overriding the default theme:

```tsx {3,9,10,11,12,13,14,15,16,17,22,52}
import {
  // ...rest of the imports
  ThemeProvider,
} from "stream-chat-react-native";
import { createStackNavigator } from "@react-navigation/stack";

// ... rest of the components

const myTheme: DeepPartial<Theme> = {
  poll: {
    message: {
      container: {
        backgroundColor: "pink",
      },
    },
  },
};

const ChannelScreen = () => {
  const navigation = useNavigation();
  return (
    <ThemeProvider style={myTheme}>
      <OverlayProvider>
        <Chat client={client}>
          <Channel
            channel={channel}
            PollContent={MyPollContent}
            openPollCreationDialog={({ sendMessage }) =>
              navigation.navigate("CreatePollScreen", { sendMessage })
            }
          >
            <ChannelStack.Navigator initialRouteName={"ChannelMessageList"}>
              <ChannelStack.Screen
                name={"ChannelMessageList"}
                options={{ headerShown: false }}
                component={ChannelMessageList}
              />
              <ChannelStack.Screen
                name={"PollResultsScreen"}
                options={{ headerShown: false }}
                component={PollResultsScreen}
              />
              <ChannelStack.Group screenOptions={{ presentation: "modal" }}>
                <ChannelStack.Screen
                  name={"CreatePollScreen"}
                  options={{ headerShown: false }}
                  component={MyCreatePollContent}
                />
              </ChannelStack.Group>
            </ChannelStack.Navigator>
          </Channel>
        </Chat>
      </OverlayProvider>
    </ThemeProvider>
  );
};
```

which gives us a changed `Poll` background:

![Custom Poll Background](@chat-sdk/react-native/v8/_assets/guides/custom-poll-flow/custom-poll-background.png)

<admonition type="note">

Make sure that the `theme` is set above your `OverlayProvider` to make sure that the `Poll` customizations are also reflected on the message preview whenever it's long pressed.

</admonition>

Finally, if you want a custom `PollAnswersList` (the default UI doesn't fit), you can replace it.

First, add `ShowAllCommentsButton` and `AddCommentButton` to your custom buttons UI:

```tsx {3,4,14,15}
import {
  // ...rest of the imports
  ShowAllCommentsButton,
  AddCommentButton
} from 'stream-chat-react-native';
import { createStackNavigator } from '@react-navigation/stack';

// ... rest of the components

const MyPollButtons = () => {
  return (
    <>
      <ShowAllOptionsButton />
      <ShowAllCommentsButton />
      <AddCommentButton />
      <ViewResultsButton
        onPress={({ message, poll }) =>
          navigation.navigate('PollResultsScreen', {
            message,
            poll,
          });
        }
      />
      <EndVoteButton />
    </>
  )
}

// ... the ChannelScreen component
```

To create comments, enable answers/comments when creating the poll, then use the `Add Comment` button:

![Step 1-1](@chat-sdk/react-native/v8/_assets/guides/custom-poll-flow/custom-comments-creation-1.png)

![Step 1-2](@chat-sdk/react-native/v8/_assets/guides/custom-poll-flow/custom-comments-creation-2.png)

As with other screens, add a dedicated navigation screen for `AnswersList`. For now, use the default UI:

```tsx {3,13,14,15,16,17,18,19,20,35,36,37,38,39,40,41,42,43,44,45,46,47,71,72,73,74,75}
import {
  // ...rest of the imports
  PollAnswersList,
} from 'stream-chat-react-native';
import { createStackNavigator } from '@react-navigation/stack';

// ... rest of the components

const MyPollButtons = () => {
  return (
    <>
      <ShowAllOptionsButton />
      <ShowAllCommentsButton
          onPress={({ message, poll }) => {
            navigation.navigate('PollAnswersScreen', {
              message,
              poll,
            });
          }}
        />
      <AddCommentButton />
      <ViewResultsButton
        onPress={({ message, poll }) =>
          navigation.navigate('PollResultsScreen', {
            message,
            poll,
          });
        }
      />
      <EndVoteButton />
    </>
  )
}

const PollAnswersScreen = ({
  route: {
    params: { message, poll },
  },
}) => {
  const navigation = useNavigation();
  return (
    <SafeAreaView style={{ flex: 1 }}>
      <PollModalHeader title={'All Poll Answers'} onPress={() => navigation.goBack()} />
      <PollAnswersList message={message} poll={poll} />
    </SafeAreaView>
  );
};

const ChannelScreen = () => {
  const navigation = useNavigation();
  return (
    <ThemeProvider style={myTheme}>
      <OverlayProvider>
        <Chat client={client}>
          <Channel
            channel={channel}
            PollContent={MyPollContent}
            openPollCreationDialog={({ sendMessage }) => navigation.navigate('CreatePollScreen', { sendMessage })}
          >
            <ChannelStack.Navigator initialRouteName={'ChannelMessageList'}>
              <ChannelStack.Screen
                name={'ChannelMessageList'}
                options={{ headerShown: false }}
                component={ChannelMessageList}
              />
              <ChannelStack.Screen
                name={'PollResultsScreen'}
                options={{ headerShown: false }}
                component={PollResultsScreen}
              />
              <ChannelStack.Screen
                name={'PollAnswersScreen'}
                options={{ headerShown: false }}
                component={PollAnswersScreen}
              />
              <ChannelStack.Group screenOptions={{ presentation: 'modal' }}>
                <ChannelStack.Screen
                  name={'CreatePollScreen'}
                  options={{ headerShown: false }}
                  component={MyCreatePollContent}
                />
              </ChannelStack.Group>
            </ChannelStack.Navigator>
          </Channel>
        </Chat>
      </OverlayProvider>
    </ThemeProvider>
  );
};
```

which will give allow us to navigate to the default `PollAnswersList` UI:

![Answer List Navigation](@chat-sdk/react-native/v8/_assets/guides/custom-poll-flow/answer-list-navigation.png)

Now, let's finally customize the UI. To achieve this we can override the `PollAnswersListContent` of our `PollAnswersList`.

Since the list of answers can be very large and we want to be able to still display all answers, we will use the `usePollAnswersPagination` hook to get them:

```tsx {3,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32,33,44}
import {
  // ...rest of the imports
  usePollAnswersPagination,
} from "stream-chat-react-native";
import { createStackNavigator } from "@react-navigation/stack";

// ... rest of the components

const LoadingIndicator = () => {
  /* some LoadingIndicator logic here */
};

const MyItem = ({ item }) => {
  const { answer_text, user } = item;
  return (
    <Text>
      {user.name} commented: {answer_text}
    </Text>
  );
};

const MyPollAnswersContent = () => {
  const { pollAnswers, loading, loadMore } = usePollAnswersPagination();
  return (
    <FlatList
      contentContainerStyle={{ flex: 1, padding: 16 }}
      data={pollAnswers}
      renderItem={MyItem}
      onEndReached={loadMore}
      ListFooterComponent={loading ? <LoadingIndicator /> : null}
    />
  );
};

const PollAnswersScreen = ({
  route: {
    params: { message, poll },
  },
}) => {
  const navigation = useNavigation();
  return (
    <SafeAreaView style={{ flex: 1 }}>
      <PollModalHeader
        title={"All Poll Answers"}
        onPress={() => navigation.goBack()}
      />
      <PollAnswersList
        message={message}
        poll={poll}
        PollAnswersListContent={MyPollAnswersContent}
      />
    </SafeAreaView>
  );
};

// ... the Channel screen
```

And we get the final content:

![Custom Answers List](@chat-sdk/react-native/v8/_assets/guides/custom-poll-flow/custom-answers-list.png)

The list will be fully compatible with loading the pagination when scrolling to the bottom and displaying a loading indicator whenever that happens as well.

With that, we have finished the customizations we wanted to do for our polls.

As a last note; any components that you'd like to reuse from the default UI are free to be imported from within the SDK.

An extensive list of these includes:

- All buttons mentioned [here](/chat/docs/sdk/react-native/v8/ui-components/poll-buttons/)
- `CreatePollContent`
- `PollContent`
- `PollButtons`
- `PollHeader`
- `PollModalHeader`
- `PollInputDialog`
- `CreatePollIcon`
- `PollOption`
- `PollResultsContent`
- `PollResultsItem`
- `PollVote`
- `PollAllOptionsContent`


---

This page was last updated at 2026-04-17T17:33:45.444Z.

For the most recent version of this documentation, visit [https://getstream.io/chat/docs/sdk/react-native/v8/guides/custom-poll-flow/](https://getstream.io/chat/docs/sdk/react-native/v8/guides/custom-poll-flow/).