Skip to content

chore(nimbus): Steps component#1005

Open
jaikamat wants to merge 19 commits intomainfrom
CRAFT-1743
Open

chore(nimbus): Steps component#1005
jaikamat wants to merge 19 commits intomainfrom
CRAFT-1743

Conversation

@jaikamat
Copy link
Contributor

@jaikamat jaikamat commented Feb 2, 2026

Summary

Adds the Steps component - a display-only progress indicator for multi-step
processes like forms, wizards, and onboarding flows.

Figma link here

Features

  • Compound component API: Steps.Root, Steps.List, Steps.Item,
    Steps.Indicator, Steps.Separator, Steps.Content, Steps.Label,
    Steps.Description
  • Three sizes: xs, sm, md
  • Two orientations: horizontal, vertical
  • Indicator types: Numeric (with checkmark on complete) or custom icons
  • State derivation: Automatically calculates complete, current,
    incomplete states from step index
  • Accessible: ARIA roles (list, listitem), aria-current="step", screen
    reader labels

Usage

<Steps.Root step={1} count={3}>
  <Steps.List>
    <Steps.Item index={0}>
      <Steps.Indicator type="numeric" />
      <Steps.Content>
        <Steps.Label>Account</Steps.Label>
        <Steps.Description>Create your account</Steps.Description>
      </Steps.Content>
    </Steps.Item>
    <Steps.Separator />
    <Steps.Item index={1}>
      <Steps.Indicator type="icon" icon={<PersonIcon />} />
      <Steps.Content>
        <Steps.Label>Profile</Steps.Label>
      </Steps.Content>
    </Steps.Item>
  </Steps.List>
</Steps.Root>

@jaikamat jaikamat requested a review from a team February 2, 2026 17:45
@jaikamat jaikamat self-assigned this Feb 2, 2026
@jaikamat jaikamat requested review from ByronDWall, ddouglasz, misama-ct, stephsprinkle, tylermorrisford and valoriecarli and removed request for a team February 2, 2026 17:45
@jaikamat jaikamat added the enhancement New feature or request label Feb 2, 2026
@vercel
Copy link

vercel bot commented Feb 2, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
nimbus-documentation Ready Ready Preview, Comment Feb 5, 2026 8:16pm
nimbus-storybook Ready Ready Preview, Comment Feb 5, 2026 8:16pm

Request Review

@changeset-bot
Copy link

changeset-bot bot commented Feb 2, 2026

⚠️ No Changeset found

Latest commit: fa6e77d

Merging this PR will not cause a version bump for any packages. If these changes should not result in a new version, you're good to go. If these changes should result in a version bump, you need to add a changeset.

This PR includes no changesets

When changesets are added to this PR, you'll see the packages that this PR includes changesets for and the associated semver types

Click here to learn what changesets are, and how to add one.

Click here if you're a maintainer who wants to add a changeset to this PR

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have to say it was pretty wild to see Claude ingest the Figma spec using the Figma MCP and derive these values directly from it. Made the job much, much easier.

### With descriptions

Add optional descriptions below labels to provide additional context for each step:

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Any idea how to handle this case? Squeezing would look unprofessional, hiding steps is kinda meh, horizontal scrollbars ugly. Or should we just trust consumers to switch orientation if necessary?

Image

Copy link
Contributor Author

@jaikamat jaikamat Feb 3, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm of a mind to push this onto consumers (for now), we could program the component to aggressively wrap text to preserve x-overflow, but I don't know that it's necessary to ship it as-is

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

see also the 'container queries' example here: https://piccalil.li/blog/a-workaround-for-using-custom-properties-in-media-queries/#downsides

It should be possible to grab the width of the steps parent wrapper, width of the wrapper for the steps, and compare them, and if the parent wrapper is smaller then update the styling for each step

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we could indeed leverage container queries, but through the existing conditionals-system.

We could use the large sizes tokens to create default container-queries.

Add to system:

import { defineConfig, createSystem, defaultConfig } from "@chakra-ui/react"

const config = defineConfig({
  conditions: {
    // Custom container query condition
    cq3xl: "@container (min-width: 768px)",
    cqSm: "@container (min-width: 384px)",
    // ...
  },
})

export const system = createSystem(defaultConfig, config);

Usage:

<Stepper orientation={{
  cqSm: 'vertical',
  cq3xl: 'horizontal',
}} />

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We have an example story for this now that dynamically switches. Consumers specify it at the moment. If we feel we want to internalize this, we can, but I ran out of time today.

Copy link
Collaborator

@misama-ct misama-ct left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Couple of things. Happy, we're still needed.

key: "nimbusSteps",
});

export const StepsRootSlot: SlotComponent<HTMLDivElement, StepsRootSlotProps> =
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not that it's a harm, but is SlotComponent as type still needed now that we generate the recipe types on the client side?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm, I'm not sure actually, might need more exploration since we have 20+ components that use this pattern.

Maybe we can scope it to another unit of work

Copy link
Contributor

@ddouglasz ddouglasz left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🦾🤖🫳🏻🎤

Copy link
Contributor

@ByronDWall ByronDWall left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a good start, but I have significant questions. The biggest one is around controls - I think we need persistent next/previous buttons.

I also really think we should only display the active content, similar to how tabs work, rather than show all steps on the page.

Also, I have some styling concerns.

},
"&[data-state='complete']": {
borderColor: "primary.9",
color: "colorPalette.contrast",
Copy link
Contributor

@ByronDWall ByronDWall Feb 5, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I believe contrast is meant to be a background color that will provide an acceptable contrast ratio for any of the colors in a palette that are meant to be used for text (.10+?). Also, we don't set a colorPalette for this recipe, are we sure that this will be primary.contrast or will it be the default color palette set on ::root (neutral, I think)?

Also, it results in not being able to see the indicator title in dark mode:

Image

Comment on lines +55 to +86
borderColor: "neutral.7",
color: "neutral.12",
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

One thing chakra does in its steps component that's interesting is it allows the user to configure color palettes for the indicators. I feel like this is in keeping with nimbus' stance on configurability, but also we don't need to implement it

Comment on lines 55 to 109
/**
* # Steps.Root
*
* Container component that manages step state and provides context to child components.
*/
Root: StepsRoot,

/**
* # Steps.List
*
* Flex container that wraps all step items and separators.
*/
List: StepsList,

/**
* # Steps.Item
*
* Container for a single step. Automatically derives state from its index.
*/
Item: StepsItem,

/**
* # Steps.Indicator
*
* Visual indicator showing step number or icon.
*/
Indicator: StepsIndicator,

/**
* # Steps.Separator
*
* Visual line connecting step indicators.
*/
Separator: StepsSeparator,

/**
* # Steps.Content
*
* Container for step label and optional description.
*/
Content: StepsContent,

/**
* # Steps.Label
*
* Displays the step title.
*/
Label: StepsLabel,

/**
* # Steps.Description
*
* Displays optional hint text below the label.
*/
Description: StepsDescription,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These are the descriptions users actually see in the ts autocomplete - I think they could be expanded a bit?

const [styleProps, functionalProps] = extractStyleProps(restProps);

return (
<StepsContentSlot
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's super weird to me that the content for all the steps is always on the page - shouldn't each step act more like a tab where only the active panel is visible?

Comment on lines +338 to +353
<Button
variant="outline"
size="sm"
onClick={() => setCurrentStep(Math.max(0, currentStep - 1))}
isDisabled={currentStep === 0}
>
Previous
</Button>
<Button
variant="solid"
size="sm"
onClick={() => setCurrentStep(Math.min(4, currentStep + 1))}
isDisabled={currentStep === 4}
>
Next
</Button>
Copy link
Contributor

@tylermorrisford tylermorrisford Feb 5, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These buttons are acting funny(same for the 'step states' example in dev docs). Claude really is hot for size="sm" to implemented I guess.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fwiw, there was a PR in flight to enable small size buttons, I just hadn't rebased yet

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants