Skip to content

HowToSteps

A guide the reader walks through: numbered steps down one side, one step's instructions at a time beside them, and a way forward under those. It ends, and says so.

tsx
import { HowToSteps } from 'neba';

<HowToSteps
  title="Schedule a job with cron"
  steps={[
    { title: 'Open your crontab', content: 'crontab -e opens yours in $EDITOR.' },
    { title: 'Write the schedule', content: 'Five fields, then the command.' }
  ]}
/>;

Props

PropTypeDefaultDescription
steps * HowToStep[]The steps, in the order they are to be done. An array rather than children because the list beside the body and the body itself are two renderings of the same data, and the panel is sized against every step rather than the one showing
titleReactNodeThe guide's own heading, over both columns
headingLevel2 | 3 | 4 | 53Which heading level title is written at, with a step's title one below it. A level is a claim about the page rather than about the component: a guide under an h1 is an h2, and the same guide inside a section is an h4
stepnumberWhich step is showing. Pass it with onStepChange to drive the guide yourself
defaultStepnumber0Where an uncontrolled guide starts
onStepChange(step: number) => voidFires with the index whenever the step changes, however it changed
completedbooleanWhether the guide is finished. Controllable too
defaultCompletedbooleanfalseWhether an uncontrolled guide starts finished
onCompletedChange(completed: boolean) => voidFires when the guide is finished, and again when it is started over
orientationshared'horizontal' | 'vertical''vertical'Which way the list runs. vertical puts the numbers down one side with the body beside them, stacking below sm; horizontal runs them across the top, and is only honest while every title is short
maxHeightnumber | stringHow tall the guide may get before it scrolls. The list and the body scroll inside it rather than the sheet growing, and the current row is kept in view. Numbers are pixels
railWidthnumber | string'15rem'How wide the list is while it is a column. Numbers are pixels
navigationbooleantrueThe row of buttons under the body. Off, the list is the only way to move: for a guide inside a page that has navigation of its own
dividerbooleantrueA hairline between the list and the body: down the inner edge while they are two columns, along the bottom of the list once they have stacked
transitionsharedNebaTransition | 'none''fade'How a step arrives when the reader moves to it, from the same vocabulary transition uses everywhere. none turns it off. It runs on the panel rather than on anything that is pressed, and a reduced-motion preference switches it off entirely
completionbooleantrueWhether there is a finished state at all. On, the last step's button says Done and pressing it replaces the body with a panel that says so. Off, the last step is simply the last step
completedContentReactNodelocale('All steps complete')What the finished panel says
variantshared'solid' | 'outline' | 'text''outline'Weight of the sheet, said the way a container says it. The sheet is never dyed: what carries the family is the numbers, the connector and the buttons
sizeshared'xs' | 'sm' | 'md' | 'lg' | 'xl''md'The disc's diameter, the type scale, and the buttons' size
colorshared'primary' | 'secondary' | 'success' | 'warning' | 'danger' | 'info''primary'The family the numbers, the connector and the buttons wear
densityshared'default' | 'compact''default'Padding only
elevationshared0 | 1 | 2 | 30Drop shadow depth. 0 is no shadow at all
localestring'en'Which language the four buttons and the closing sentence are in. Unsupported tags fall back to English
previousLabel · nextLabel · doneLabel · restartLabelstringlocaleThose four words, written out

Every native <div> attribute passes through, apart from color, title and content, which the component owns. The shared axes are described under prop conventions.

HowToStep

PropTypeDefaultDescription
title * ReactNodeThe heading, shown both in the list and over the step's own body
iconReactNodeA glyph before the title over the step's own body. Not in the list: a row there already carries a numbered disc, and a glyph beside it is a second mark making the same claim
contentReactNodeWhat the reader has to do: prose, a CodeBlock, a form
imagestringA picture above the content, for a step that is easier shown than said
imageAltstringtitleWhat that picture says for a reader who cannot see it

The steps are an array rather than children, which is the one place this component could not be built the other way: the list beside the body and the body itself are two renderings of the same data, and the panel is sized against every step rather than the one showing.

Examples

orientation

vertical is the default: the numbers run down one side with the body beside them, which takes any number of steps and any amount to say about each. Below sm it stacks. horizontal runs the numbers across the top, and is only honest while every title is short.

maxHeight

How tall the guide may get before it scrolls: a number is pixels. The list and the body scroll inside it rather than the sheet growing, and the current row is kept in view as the step changes.

step · completed

Both states are controllable. Pass step with onStepChange to keep the position yourself (in a URL, in a form's state), and completed with onCompletedChange for the end.

icon

Each step takes a glyph, drawn before the title over its own body. Only there: a row in the list already carries a numbered disc, and a glyph beside it is a second mark making the same claim. What an icon is good for is saying what kind of step this is: a terminal, a file, a warning.

tsx
{ title: 'Open your crontab', icon: <TerminalIcon />, content: … }

divider

A hairline between the list and the body: down the inner edge while they are two columns, along the bottom of the list once they have stacked. On by default: the two are different kinds of thing, and space alone leaves that to a gap a narrow screen is about to take away.

transition

How a step arrives when the reader moves to it, from the same vocabulary transition uses everywhere: an effect name, or the object form for the duration, the easing, the direction. 'none' turns it off, and a reduced-motion preference does too.

It runs on the panel and never on anything that is pressed: the buttons and the list rows hold still, and what animates is the content they changed.

navigation={false} drops the row of buttons and leaves the list as the only way to move, for a guide inside a page that has navigation of its own. completion={false} removes the finished state entirely: the last step is simply the last step.

variant · size · color

The three weights say what they say everywhere, and the sheet is never dyed by color: what carries the family is the numbers, the connector and the buttons. text is the one to reach for inside a Card, which is already a sheet.

headingLevel

title is drawn as an <h3> and a step's title one level below it, at <h4>. headingLevel moves that starting point, because a level is a claim about the page rather than about the component: a guide sitting directly under an <h1> should be an <h2>, and the same guide inside a section should be an <h4>.

tsx
<HowToSteps steps={steps} title="Getting started" headingLevel={2} />

Steps with anything in them

content takes a node, so a step can hold a CodeBlock, a screenshot through image, a form, or another component entirely. The panel keeps the height of the tallest step, so a step with a code block in it does not resize the card when the reader reaches it, and nothing is remounted as the step changes, so a form halfway through a guide still holds what was typed into it.

Accessibility

  • The list is a list of buttons, not a tablist. The current row carries aria-current="step", which says the panels are ordered and the reader is expected to arrive at them in that order.
  • Each row is read as "Step 3: Use it". The disc is decoration, and a number drawn beside a title is not a number a screen reader announces. A title that is a node is read as itself instead, there being no string to build that sentence out of.
  • The steps that are not showing stay in the document so the panel can keep its height, and are inert: out of the tab order, off the accessibility tree, and out of a find-in-page.
  • Give the guide a title when a page has more than one.

Released under the MIT License