# Channel and thread side by side

> **Info:** This is release candidate documentation for **Stream Chat React SDK v15**. For the latest stable version, see the [latest version (v14)](https://getstream.io/chat/docs/sdk/react/).

This recipe shows the open channel with a thread beside it. The channel list sits on the left, the channel takes the space in the middle, and the thread opens in a 400px column on the right. Closing the thread gives its space back to the channel.

It starts from the app in [Add threads](https://getstream.io/chat/docs/sdk/react/v15/getting-started/quick-start/#add-threads), which already has a slot for each, and adds four things:

- an empty state in the channel's place until the user picks a channel;
- a rule that puts every thread in the channels view in the `thread` slot;
- the hook that tells the thread it's on screen, so its new replies are marked read;
- a fixed width for the thread.

## A layout with two slots

```tsx title="src/App.tsx"
import { useEffect } from "react";
import { ChannelPaginator } from "stream-chat";
import type { ChannelFilters, SortParamRequest } from "stream-chat";
import {
  ChannelHeader,
  ChannelNavigation,
  Chat,
  MessageComposer,
  MessageList,
  ThreadHeader,
  ThreadList,
  useCreateChatClient,
  useThreadContext,
} from "stream-chat-react";
import {
  ChannelSlot,
  ChatView,
  ChatViewEmptyPlaceholder,
  ThreadSlot,
  useActiveThread,
} from "stream-chat-react/slot-layout";
import type {
  ChatViewProps,
  LayoutDescriptor,
} from "stream-chat-react/slot-layout";
import "stream-chat-react/dist/css/index.css";
import "./layout.css";

const apiKey = "YOUR_API_KEY";
const userId = "YOUR_USER_ID";
const userToken = "YOUR_USER_TOKEN";

const filters: ChannelFilters = {
  members: { $in: [userId] },
  type: "messaging",
};
const sort: SortParamRequest[] = [{ direction: -1, field: "last_message_at" }];

// The channels view has a slot for the open channel and, after it, one for a
// thread opened from it. The threads view has one slot, for the thread picked
// in the list.
const layouts: LayoutDescriptor[] = [
  { id: "channels", slots: ["channel", "thread"] },
  { id: "threads", slots: ["thread"] },
];

// In the channels view, every thread opens in the "thread" slot.
const viewActionSlotResolvers: ChatViewProps["viewActionSlotResolvers"] = {
  channels: { openThread: () => "thread" },
};

// Marks the thread as on screen while the window is visible and focused.
const ActiveThread = () => {
  const thread = useThreadContext();
  useActiveThread({ activeThread: thread });
  return null;
};

const ThreadPanel = () => (
  <ThreadSlot slot="thread">
    <ActiveThread />
    <ThreadHeader />
    <MessageList />
    <MessageComposer />
  </ThreadSlot>
);

const ChannelsView = () => (
  <>
    <ChatView.Selector />
    <ChannelNavigation />
    <ChannelSlot fallback={<ChatViewEmptyPlaceholder />} slot="channel">
      <ChannelHeader />
      <MessageList />
      <MessageComposer />
    </ChannelSlot>
    <ThreadPanel />
  </>
);

const ThreadsView = () => (
  <>
    <ChatView.Selector />
    <ThreadList />
    <ThreadPanel />
  </>
);

const App = () => {
  const client = useCreateChatClient({
    apiKey,
    tokenOrProvider: userToken,
    userData: { id: userId },
  });

  useEffect(() => {
    if (!client) return;
    const paginator = new ChannelPaginator({
      client,
      filters,
      id: "my-channels",
      sort,
    });
    client.channelManager.insertPaginator({ paginator });
    return () => {
      client.channelManager.removePaginator(paginator);
    };
  }, [client]);

  if (!client) return <div>Connecting...</div>;

  return (
    <Chat client={client}>
      <ChatView
        layouts={layouts}
        viewActionSlotResolvers={viewActionSlotResolvers}
        views={{ channels: <ChannelsView />, threads: <ThreadsView /> }}
      />
    </Chat>
  );
};

export default App;
```

```css title="src/layout.css"
html,
body,
#root {
  height: 100%;
}

body {
  margin: 0;
}

.str-chat {
  --str-chat__channel-list-width: 320px;
  --str-chat__thread-list-width: 320px;
}

/* Beside the channel, the thread is a 400px column. The channel takes the rest. */
.str-chat__chat-view__channels > .str-chat__thread-container {
  flex: 0 0 400px;
}
```

`layouts` gives the channels view two slots, `channel` and then `thread`. The view's content places them side by side:

- `ChannelSlot` renders the channel in the `channel` slot. Until the user picks one, it renders its `fallback`: `ChatViewEmptyPlaceholder` is the SDK's empty state, "No chat selected".
- `ThreadSlot` renders the thread in the `thread` slot, after the channel. While no thread is open, it renders nothing, so the channel takes the full width.

`ThreadPanel` is in both views. In the threads view, its `thread` slot is the view's only slot, and the thread fills the space beside the thread list.

In the CSS, the selector `.str-chat__chat-view__channels` matches only the channels view, so only the thread beside a channel gets the fixed width. [Workspace with ChatView](https://getstream.io/chat/docs/sdk/react/v15/ui/layout/workspace-with-chatview/#views-and-slots) explains views and slots in full.

## Opening a thread beside the channel

The replies button under a message, and the reply actions on it, open its thread. Here is what happens when the user clicks "3 replies" under a message in the open channel:

1. The button calls `openThread` with the channel and the message. `openThread` gets the message's thread from `client.threads`, which creates it if it doesn't exist yet.
2. A thread opens in the view that's shown, here the channels view. `viewActionSlotResolvers` picks its slot: `thread`.
3. `ThreadSlot` renders a `Thread` for it, around the header, the message list and the composer. The thread loads its replies.
4. `ActiveThread` calls `useActiveThread`. The SDK doesn't tell a thread it's on screen; this hook does, while the window is visible and focused. Then the thread's new replies are marked read, and a thread that went out of date, after a lost connection for example, reloads.

Without `viewActionSlotResolvers`, `ChatView` picks the slot by [its own rules](https://getstream.io/chat/docs/sdk/react/v15/ui/layout/workspace-with-chatview/#where-an-opened-channel-or-thread-lands). While a channel is open, they pick `thread` too. With no channel open, they would pick the first empty slot, `channel`, where `ChannelSlot` doesn't show a thread. The resolver keeps a thread opened from your own code, such as a notification, beside the empty state.

Opening another thread replaces the one beside the channel, because that slot already holds a thread. The other ways to open a thread, and how to add your own button for it, are in [Opening a thread](https://getstream.io/chat/docs/sdk/react/v15/ui/layout/moving-between-channels-threads-and-messages/#opening-a-thread).

## Closing the thread

Three things close the thread beside the channel:

- **The close button in the thread's header.** `ThreadHeader` renders it on a thread beside a channel. It empties the `thread` slot. `ThreadSlot` then renders nothing, and the channel takes the full width.
- **Opening a channel.** A click on a channel in the list or in the search results closes the thread, even when it's the channel that's already open.
- **Your own control.** `useCloseThread()` returns the function the close button calls. [Back and close](https://getstream.io/chat/docs/sdk/react/v15/ui/layout/moving-between-channels-threads-and-messages/#back-and-close) shows a custom thread header that uses it.

In the threads view, the thread is the view's main panel, so its header has no close button. The user picks another thread or switches views.

On a phone there's no room for two columns. Below 768px, the SDK's stylesheet also hides the channel list. [Single-pane mobile with back button](https://getstream.io/chat/docs/sdk/react/v15/recipes/layout/single-pane-mobile-with-back-button/) shows one panel at a time, and [Responsive layouts](https://getstream.io/chat/docs/sdk/react/v15/ui/layout/responsive-layouts/) lays the thread over the channel instead.

---

For the most recent version of this documentation, visit [https://getstream.io/chat/docs/sdk/react/v15/recipes/layout/channel-and-thread-side-by-side/](https://getstream.io/chat/docs/sdk/react/v15/recipes/layout/channel-and-thread-side-by-side/).