Customizing the in-app feed components

Learn how to override the default styles of the pre-built React in-app feed components that Knock provides.

As a refresher, our in-app feed components let you drop in a ready-to-use in-app notification feed into your product, powered by Knock's in-app feed and real-time service.

In some applications, you may want to override the styles provided in the React components, or even replace those styles completely. In this guide, we'll look at the options you have for customizing the styles provided with the Knock React components.

Customizing the CSS variables

If you're importing the CSS styles associated with the components, then it's possible to use CSS variables to override the majority of the properties used within the stylesheet, such as colors, font sizes, ad more.

The CSS variables that are used within the components are all prefixed with --rnf-. The complete set of overrides is here, for reference, or available in the theme.css file.

1:root {
2  /* Font sizes */
3  --rnf-font-size-xs: 0.75rem;
4  --rnf-font-size-sm: 0.875rem;
5  --rnf-font-size-md: 1rem;
6  --rnf-font-size-lg: 1.125rem;
7  --rnf-font-size-xl: 1.266rem;
8  --rnf-font-size-2xl: 1.5rem;
9  --rnf-font-size-3xl: 1.75rem;
10
11  /* Spacing */
12  --rnf-spacing-0: 0;
13  --rnf-spacing-1: 4px;
14  --rnf-spacing-2: 8px;
15  --rnf-spacing-3: 12px;
16  --rnf-spacing-4: 16px;
17  --rnf-spacing-5: 20px;
18  --rnf-spacing-6: 24px;
19  --rnf-spacing-7: 32px;
20  --rnf-spacing-8: 42px;
21
22  /* Font weights */
23  --rnf-font-weight-normal: 400;
24  --rnf-font-weight-medium: 500;
25  --rnf-font-weight-semibold: 600;
26  --rnf-font-weight-bold: 700;
27
28  /* Font family */
29  --rnf-font-family-sanserif: Inter, -apple-system, BlinkMacSystemFont, "Segoe UI",
30    Roboto, Ubuntu, "Helvetica Neue", sans-serif;
31
32  /* Border radius */
33  --rnf-border-radius-sm: 2px;
34  --rnf-border-radius-md: 4px;
35  --rnf-border-radius-lg: 8px;
36
37  /* Shadows */
38  --rnf-shadow-sm: 0px 5px 10px rgba(0, 0, 0, 0.12);
39  --rnf-shadow-md: 0px 8px 30px rgba(0, 0, 0, 0.24);
40
41  /* Colors */
42  --rnf-color-white: #fff;
43  --rnf-color-white-a-75: rgba(255, 255, 255, 0.75);
44  --rnf-color-black: #000;
45  --rnf-color-gray-900: #1a1f36;
46  --rnf-color-gray-800: #3c4257;
47  --rnf-color-gray-700: #3c4257;
48  --rnf-color-gray-600: #515669;
49  --rnf-color-gray-500: #697386;
50  --rnf-color-gray-400: #9ea0aa;
51  --rnf-color-gray-300: #a5acb8;
52  --rnf-color-gray-200: #dddee1;
53  --rnf-color-gray-100: #e4e8ee;
54  --rnf-color-brand-500: #e95744;
55  --rnf-color-brand-700: #e4321b;
56  --rnf-color-brand-900: #891e10;
57
58  /* Component specific colors */
59  --rnf-unread-badge-bg-color: #dd514c;
60  --rnf-avatar-bg-color: #ef8476;
61  --rnf-message-cell-unread-dot-bg-color: #f4ada4;
62  --rnf-message-cell-hover-bg-color: #f1f6fc;
63}

Within your own application, you can add overrides by targeting the same properties and ensuring your styles are loaded after the CSS of the component. For example, to override the unread badge color:

feed-overrides.css
1:root {
2  --rnf-unread-badge-bg-color: #cccccc;
3}

Then we'd import like:

1// Default styles
2import "@knocklabs/react/dist/index.css";
3
4// Overrides
5import "~/styles/feed-overrides.css";

Overriding the component CSS

If you need more fine-grained control over the CSS provided by the components, it's also possible to override the CSS that's provided by the components, entirely or partially.

All of the notification feed components have classes prefixed with rnf-. You can see the existing CSS styles here, which are written using CSS modules to be isolated per component.

Partially overriding the feed CSS

To partially override the CSS of the feed, we can rely on the cascading nature of CSS to ensure that any overrides we define take precedence over the base styles.

Note: depending on how you have your CSS imports defined in your application, you may have to use !important declarations to override the styles that the library defines.

Let's look at an example where we override the color of the unread dot:

feed-overrides.css
1.rnf-notification-cell__unread-dot {
2  background-color: #cccccc;
3}

And when we import those styles, we'll want to ensure our overrides are imported after we import the base CSS styles for the feed.

1// Default styles
2import "@knocklabs/react/dist/index.css";
3
4// Overrides
5import "~/styles/feed-overrides.css";

Fully overriding the feed CSS

To override all of the CSS provided by the components you should not import the base CSS that the components exports. This will mean that you then need to provide CSS for each of the components that the feed renders yourself.

Please refer to the CSS class names that the components use for the classes that you'll need to add.

Rendering custom feed cells

If you need to control the style or behavior of each item rendered in the feed, you can use the renderItem callback prop within the NotificationFeed or NotificationFeedPopover to render custom feed cells.

The renderItem prop receives a set of props, including the FeedItem and is called for every item being rendered in the feed. From here, you can render your own feed cell, complete with any custom styles or interactions you might need.

Here's an example:

Custom feed cell
1const CustomNotificationCell = ({ item, onItemClick }) => (
2  <Container>
3    {item.actor && <Avatar name={item.actor.name} src={item.actor.avatar} />}
4    <Inner>
5      <div dangerouslySetInnerHTML={{ __html: item.blocks[0].rendered }} />
6    </Inner>
7  </Container>
8);

And to render the custom cell in your NotificationFeedPopover or NotificationFeed component:

Custom cell rendering
1<NotificationFeedPopover
2  renderItem={(props) => <CustomNotificationCell {...props} />}
3/>

Rendering custom UI

If you need even more control over the components rendered in the feed, it's also possible to bring your own set of components and use either the provided hooks or KnockFeedProvider component to run Knock in a "headless" manner. Using this option gives you the most control over the style and interaction with the components, with the trade-off that you have to provide the components yourself.

Using hooks to render custom UI

The React component library ships with hooks that you can use to set up and manage your Knock client and feed connection. You can then use these hooks to provide the data for your own custom feed components. Let's take a look at what that looks like.

First of all, we need to use the useAuthenticatedKnockClient hook to create a Knock client instance, authenticated for the user that's provided.

Setting up a Knock client
1import { useAuthenticatedKnockClient } from "@knocklabs/react";
2
3const NotificationFeed = ({ user }) => {
4  const knockClient = useAuthenticatedKnockClient(
5    process.env.KNOCK_PUBLIC_API_KEY,
6    user.id,
7  );
8
9  return null;
10};

Next up, we then set up our feed instance using the useNotifications hook. This hook expects to receive a Knock client instance, as well as information about the feed that we wish to connect to.

Using our notifications hook
1import {
2  useAuthenticatedKnockClient,
3  useNotifications,
4} from "@knocklabs/react";
5
6const NotificationFeed = ({ user }) => {
7  const knockClient = useAuthenticatedKnockClient(
8    process.env.KNOCK_PUBLIC_API_KEY,
9    user.id,
10  );
11  const feed = useNotifications(knockClient, process.env.KNOCK_FEED_CHANNEL_ID);
12
13  return null;
14};

Now we have our Feed instance, then we need to have a way to access the state of that feed and initiate the fetch when the component mounts. Here we're using Zustand, which is the state management library that sits behind the feed instance.

Using the feed instance
1import {
2  useAuthenticatedKnockClient,
3  useNotifications,
4} from "@knocklabs/react";
5import create from "zustand";
6import { useEffect } from "react";
7
8const NotificationFeed = ({ user }) => {
9  const knockClient = useAuthenticatedKnockClient(
10    process.env.KNOCK_PUBLIC_API_KEY,
11    user.id,
12  );
13  const feed = useNotifications(knockClient, process.env.KNOCK_FEED_CHANNEL_ID);
14
15  const useNotificationStore = create(feed.store);
16  const { items, metadata } = useNotificationStore();
17
18  useEffect(() => feed.fetch(), [feed]);
19
20  return null;
21};

With our component set up, we have all the building blocks we need to build our in-app feed. The items that we're returning give us the list of items fetched and available in the feed, while the metadata gives us information about the total number of items in the feed as well as the unread and unseen counts, which we can use to render badges.

Using the KnockFeedProvider to render custom UI

The KnockFeedProvider can also be used to set up the feed instance and build custom UI, which is what the pre-built components use under the hood.

The first step is to set up a wrapper component that will render your Feed with the KnockProvider and KnockFeedProvider:

Setting up our feed provider
1import { KnockProvider, KnockFeedProvider } from "@knocklabs/react";
2
3const NotificationFeed = ({ user }) => (
4  <KnockProvider apiKey={process.env.KNOCK_PUBLIC_API_KEY} userId={user.id}>
5    <KnockFeedProvider
6      feedId={process.env.KNOCK_FEED_CHANNEL_ID}
7    ></KnockFeedProvider>
8  </KnockProvider>
9);

Now that our provider is set up, we're going to split out a subcomponent that will be used to render the actual contents of the feed. The reason we have this as a separate component is so we can access the context that the KnockFeedProvider exposes.

Implementing our feed component
1import { useKnockFeed } from "@knocklabs/react";
2
3const Feed = () => {
4  const { useFeedStore } = useKnockFeed();
5  const { items, metadata } = useFeedStore();
6
7  // Render your feed components here
8  return null;
9};

Note here we're using the useFeedStore hook that the useKnockFeed hook returns. This is a wrapper around the create function that Zustand exposes to make it easy to work with the state store in the feed instance.

And finally, to put it all together:

Our working custom feed
1import { KnockFeedProvider } from "@knocklabs/react";
2import Feed from "~/components/Feed";
3
4const NotificationFeed = ({ user }) => (
5  <KnockProvider apiKey={process.env.KNOCK_PUBLIC_API_KEY} userId={user.id}>
6    <KnockFeedProvider feedId={process.env.KNOCK_FEED_CHANNEL_ID}>
7      <Feed />
8    </KnockFeedProvider>
9  </KnockProvider>
10);