Learn-by-doing onboarding

This example shows how to use Dopt to build onboarding for a Kanban app that helps users learn the product by simply using it. This onboarding pattern can be effective because it is highly contextual and never in the user’s way. You can see real world examples from companies like Slack.

How it works

This is the Dopt flow that powers the in-product onboarding experience:

Dopt flow powering this example

The flow contains blocks that you connect to define the targeting and logic of the onboarding experience.

The start block defines which set of users should see the flow. All users in this case.

The step blocks store the state of an experience for a user, like active when the experience is active and complete when the experience is done.

The finish block finishes the flow, ending the experience for the user.

Developers use Dopt’s SDKs to access and update the state of step blocks to develop onboarding flows in your application. For example, we show the user a tip in the issue card when a step block is active: true.

Let’s walk through how to use the Dopt React SDK to power the tips in the card.

To start, you initialize Dopt with the <DoptProvider />. You’ll include the user ID of a user you’ve identified to Dopt and the flow versions you’d like that user to see.

The cards in the Kanban app are all using a <Card/> component. In this onboarding flow, there are three tips, each with a step block associated with it.

To build this experience, we’ll access the three tip step blocks with the useBlock hook. The step blocks contain data, state, and transitions to progress the flow.

Next, we’ll conditionally render the tips in the <Card/> component. The tips will only render when the step block state is active: true.

src/components/card.tsx
export function Card({ id, title, children, isDragging }: Props) {  const [firstIssueId] = useKeyValueStore('firstIssue');
  const [firstIssue] = useBlock('krcPrzGs9w6J2mHKQJLYd');  const [firstIssueInProgress] = useBlock('z2rnhWqUav5rhRLcxoM55');  const [firstIssueReordered] = useBlock('pMw5hcPMz3aZPr5IVfnP8');
  return (    <div className={`${cardClass} ${isDragging ? draggingClass : ''}`}>      <h3 className={titleClass}>{title}</h3>      {children && <p className={descriptionClass}>{children}</p>}      {firstIssueId === id && (        <>          {firstIssue.state.active && (            <Alert>              <Text span>                Okay, let's get to work on your issue. Drag this issue to the{' '}                <b>In Progress</b> column to change the status.              </Text>            </Alert>          )}          {firstIssueInProgress.state.active && (            <Alert>              <Text span>                Nice, this issue is now <b>In Progress</b>. Next, reorder this                item to change its priority.              </Text>            </Alert>          )}          {firstIssueReordered.state.active && (            <Alert>              <Text span>                Great, the new priority is set so your whole team can be on the                same page. Now let's move this to <b>Done 🙌</b>.              </Text>            </Alert>          )}        </>      )}    </div>  );}

Progressing the flow

As a user performs certain actions, we’ll progress the flow to move them to the next steps of the onboarding experience. Let’s look at the Drag issue to In Progress tip as an example.

When the user drags the issue card into the In Progress column, we call moveIssueIntoProgress('default') on Drag issue to In Progress tip. moveIssueIntoProgress() maps to the transition() function returned from the useBlock hook which allows you to define which path in the flow the user should transition along. In this case, the user is transitioned along the path called default. This conditional logic is wired up in code in handleMoveCard.

src/pages/index.tsx
const handleMoveCard = (  card: Card,  source: CardSource,  destination: CardDestination) => {  if (card.id === firstIssueId) {    if (      firstIssue.state.active &&      source.fromColumnId === 'backlog' &&      destination.toColumnId === 'inProgress'    ) {      moveIssueIntoProgress('default');    }  }  ...};

When moveIssueIntoProgress() is called, the step block states are updated to progress the flow as shown below.

Finishing the flow

When the state of Next steps is exited: true, the finish block will be triggered and the flow will be finished for that user.

User flow state in Dopt

Real world examples of learn-by-doing

Slack uses the learn-by-doing pattern during their onboarding to teach users how to use their messenger by sending a message.

Slack also uses the learn-by-doing pattern to help users learn how to use their @ mention.

These examples work well because Slack calls out the feature and provides help contextually, never getting in the way of the user and always helping them get to the next step. Slack also rewards users with positive reinforcement when they complete the task, which can help users feel like they’re succeeding in learning the product.

Slack onboarding and @ mention videos provided by Page Flows 🙏.