LogoSkills

coui-stepper

Activate when creating multi-step forms, wizard flows, checkout progress indicators, or step-by-step navigation using Stepper with CoreCoStepItem, CoreStepperOrientation (horizontal, vertical), Cor...

CoUI Stepper#

ํ•œ๋งˆ๋””๋กœ (๋น„๊ฐœ๋ฐœ์ž์šฉ)#

์ด ์Šคํ‚ฌ์€ "์—ฌ๋Ÿฌ ๋‹จ๊ณ„๋กœ ๋‚˜๋‰œ ํ™”๋ฉด"์„ ๋งŒ๋“œ๋Š” ๋„๊ตฌ์ž…๋‹ˆ๋‹ค. ๋งˆ์น˜ ํƒ๋ฐฐ ๋ฐฐ์†ก ์กฐํšŒ์—์„œ "์ฃผ๋ฌธ โ†’ ๊ฒฐ์ œ โ†’ ๋ฐฐ์†ก โ†’ ์™„๋ฃŒ"์ฒ˜๋Ÿผ ์ง€๊ธˆ ์–ด๋””๊นŒ์ง€ ์™”๋Š”์ง€ ๋™๊ทธ๋ž€ ๋ฒˆํ˜ธ์™€ ์—ฐ๊ฒฐ์„ ์œผ๋กœ ๋ณด์—ฌ์ฃผ๋Š” ๊ทธ ํ‘œ์‹œ๋ฅผ ๋งŒ๋“ค์–ด ์ค๋‹ˆ๋‹ค. ๊ฐ€์ž… ์ ˆ์ฐจ๋‚˜ ๊ฒฐ์ œ ๊ณผ์ •์ฒ˜๋Ÿผ ํ•œ ๋ฒˆ์— ๋‹ค ํ•˜๊ธฐ ๋ถ€๋‹ด์Šค๋Ÿฌ์šด ์ผ์„ ์ž‘์€ ๋‹จ๊ณ„๋กœ ์ชผ๊ฐœ ์•ˆ๋‚ดํ•  ๋•Œ ์”๋‹ˆ๋‹ค.

๋ฌด์—‡์„ยท์–ธ์ œ (๋น„๊ฐœ๋ฐœ์ž์šฉ)#

  • ๋ฌด์—‡์„ ํ•ด์ฃผ๋‚˜์š”: ์—ฌ๋Ÿฌ ๋‹จ๊ณ„๋กœ ์ง„ํ–‰๋˜๋Š” ํ™”๋ฉด(๋‹จ๊ณ„ ํ‘œ์‹œ์ค„)์„ ๋งŒ๋“ค์–ด ์ค๋‹ˆ๋‹ค. ๊ฐ€๋กœ/์„ธ๋กœ ๋ฐฐ์น˜, ๋‹จ๊ณ„ ํด๋ฆญ์œผ๋กœ ์ด๋™, ์ƒ‰ยทํฌ๊ธฐ ๋“ฑ ๋ชจ์–‘ ๊พธ๋ฏธ๊ธฐ๋ฅผ ์ง€์›ํ•ฉ๋‹ˆ๋‹ค. ์™„๋ฃŒํ•œ ๋‹จ๊ณ„๋Š” ์ฒดํฌ ํ‘œ์‹œ๋กœ, ์•„์ง ์•ˆ ํ•œ ๋‹จ๊ณ„๋Š” ๋ฒˆํ˜ธ๋กœ ๋ณด์—ฌ์ค๋‹ˆ๋‹ค.
  • ์–ธ์ œ ์ž‘๋™ํ•˜๋‚˜์š”: ํšŒ์›๊ฐ€์ž… ๋งˆ๋ฒ•์‚ฌ, ๊ฒฐ์ œ(์žฅ๋ฐ”๊ตฌ๋‹ˆ โ†’ ๋ฐฐ์†ก โ†’ ๊ฒฐ์ œ โ†’ ์™„๋ฃŒ) ๊ฐ™์€ ์—ฌ๋Ÿฌ ๋‹จ๊ณ„ ํผ, ๋‹จ๊ณ„๋ณ„ ์•ˆ๋‚ด ํ๋ฆ„์„ ๋งŒ๋“ค ๋•Œ ํ™œ์„ฑํ™”๋ฉ๋‹ˆ๋‹ค.
  • ์–ด๋””์— ์“ฐ๋‚˜์š”: Flutter ์•ฑ๊ณผ ์›น(Jaspr) ์–‘์ชฝ์—์„œ ๋™์ผํ•˜๊ฒŒ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

ํ•ต์‹ฌ ์šฉ์–ด (๋น„๊ฐœ๋ฐœ์ž์šฉ)#

์šฉ์–ด์‰ฌ์šด ์„ค๋ช…
Stepper(์Šคํ…Œํผ)์ง„ํ–‰ ๋‹จ๊ณ„๋ฅผ ๋ฒˆํ˜ธ์™€ ์—ฐ๊ฒฐ์„ ์œผ๋กœ ๋ณด์—ฌ์ฃผ๋Š” ๋‹จ๊ณ„ ํ‘œ์‹œ์ค„
step(์Šคํ…)์ „์ฒด ๊ณผ์ • ์ค‘ ํ•˜๋‚˜์˜ ๋‹จ๊ณ„ (์˜ˆ: "๊ฒฐ์ œ")
indicator(์ธ๋””์ผ€์ดํ„ฐ)๊ฐ ๋‹จ๊ณ„๋ฅผ ๋‚˜ํƒ€๋‚ด๋Š” ๋™๊ทธ๋ž€ ๋ฒˆํ˜ธ(๋˜๋Š” ์ฒดํฌ) ํ‘œ์‹œ
connector(์ปค๋„ฅํ„ฐ)๋‹จ๊ณ„์™€ ๋‹จ๊ณ„ ์‚ฌ์ด๋ฅผ ์ž‡๋Š” ์—ฐ๊ฒฐ์„ 
label(๋ ˆ์ด๋ธ”)๋‹จ๊ณ„ ์ด๋ฆ„ ๊ธ€์ž (์˜ˆ: "๋ฐฐ์†ก")
description(์„ค๋ช…)๋‹จ๊ณ„ ์•„๋ž˜ ๋ถ™๋Š” ๋ถ€๊ฐ€ ์„ค๋ช… ๊ธ€์ž (์„ธ๋กœ ๋ฐฐ์น˜์—์„œ๋งŒ ํ‘œ์‹œ)
orientation(๋ฐฉํ–ฅ)๋‹จ๊ณ„๋ฅผ ๊ฐ€๋กœ๋กœ ๋Š˜์–ด๋†“์„์ง€ ์„ธ๋กœ๋กœ ์Œ“์„์ง€ ๊ฒฐ์ •
horizontal/vertical๊ฐ€๋กœ ๋ฐฐ์น˜ / ์„ธ๋กœ ๋ฐฐ์น˜
style(์Šคํƒ€์ผ)์ƒ‰ยทํฌ๊ธฐยท๋‘๊ป˜ ๋“ฑ ๋‹จ๊ณ„ ํ‘œ์‹œ์ค„์˜ ๊ฒ‰๋ชจ์–‘ ์„ค์ •
Legacy(๋ ˆ๊ฑฐ์‹œ)๋” ์ด์ƒ ์“ฐ๋ฉด ์•ˆ ๋˜๋Š” ์˜›๋‚  ๋ฐฉ์‹ (์ƒˆ ๋ฐฉ์‹์œผ๋กœ ๊ต์ฒด ํ•„์š”)

Overview#

Stepper displays a multi-step process with circular indicators, connector lines, and step labels. Completed steps render a LucideIcons.check glyph; the current and pending steps render their 1-based number. It supports horizontal and vertical layouts, click-to-navigate via onStepChanged, and full chrome customisation through a single stepperStyle slot.

The canonical API (coui v0.127.15) is Stepper on both platforms, sharing CoreStepperContract from coui_core. Behaviour fields (steps, currentStep, orientation, onStepChanged) stay flat on the widget; every chrome/dimensional/nested-slot override flows through the single stepperStyle: CoreStepperStyle(...) slot, with a project-level base in CoreStepperTheme.

Resolve chain (identical on both platforms):

design-system default
  โ†’ CoreStepperTheme.style          // project-wide (coreComponentTheme.stepper)
  โ†’ widget.stepperStyle             // per-instance

Variants & Sizes#

The stepper is a layout/structural component: it has no variant enum and no size enum. There is no CoreStepperVariant and no CoreComponentSize parameter on any stepper class. The only semantic enum is the orientation โ€” full set:

ValueDescription
CoreStepperOrientation.horizontal Horizontal layout (default). Indicator above label, connectors run between steps.
CoreStepperOrientation.vertical Vertical layout. Indicator column on the left, label + optional description on the right, vertical connectors.

Indicator size, connector thickness, colours, and typography all go through CoreStepperStyle โ€” see the style slot table below.

Flutter (coui_flutter)#

Import#

import 'package:coui_flutter/coui_flutter.dart';

Note: Flutter's Material library also exports a class named Stepper. When a file imports both package:flutter/material.dart and the coui barrel, hide the Material one: import 'package:flutter/material.dart' hide Stepper;.

Horizontal Stepper (default)#

Stepper(
  currentStep: _currentStep,
  steps: const [
    CoreCoStepItem(label: 'Account'),
    CoreCoStepItem(label: 'Details'),
    CoreCoStepItem(label: 'Confirm'),
  ],
  onStepChanged: (index) => setState(() => _currentStep = index),
)

Vertical Stepper#

Stepper(
  currentStep: _currentStep,
  orientation: CoreStepperOrientation.vertical,
  steps: const [
    CoreCoStepItem(label: 'Create Account', description: 'Name and email'),
    CoreCoStepItem(label: 'Set Up Profile'),
    CoreCoStepItem(label: 'Complete'),
  ],
  onStepChanged: (index) => setState(() => _currentStep = index),
)

Stepper Parameters (Flutter)#

ParameterTypeDefaultDescription
steps List<CoreCoStepItem> required List of steps to display
currentStep int 0 Index of the currently active step
orientation CoreStepperOrientation .horizontal Layout orientation
onStepChanged ValueChanged<int>? null Called with the tapped step index when an indicator is tapped. null disables navigation (cursor stays basic)
stepperStyle CoreStepperStyle? null Per-instance chrome and nested slot overrides

CoreCoStepItem Parameters#

ParameterTypeDefaultDescription
labelStringrequiredStep label text
description String? null Optional description โ€” rendered only in the vertical layout

Rendering Rules (source-verified)#

  • Completed steps (index < currentStep): indicator shows the LucideIcons.check icon over the active fill.
  • Current step (index == currentStep): indicator shows its number over the active fill; label renders bold (unless labelStyle sets an explicit fontWeight).
  • Pending steps (index > currentStep): indicator shows its number over the inactive fill.
  • Connector segments before the current step use the active colour, the rest the inactive colour.
  • description renders only in the vertical layout, below the label.

Style Customisation#

All chrome flows through the single stepperStyle: CoreStepperStyle(...) slot:

Stepper(
  currentStep: _currentStep,
  steps: const [
    CoreCoStepItem(label: 'Step 1'),
    CoreCoStepItem(label: 'Step 2'),
    CoreCoStepItem(label: 'Step 3'),
  ],
  stepperStyle: const CoreStepperStyle(
    indicatorActiveColor: CoreColor.token(CoreColors.secondary),
    connectorActiveColor: CoreColor.token(CoreColors.secondary),
    indicatorSize: 36.0,
    connectorThickness: 2.0,
    labelStyle: CoreTextStyle(fontWeight: CoreFontWeight.semiBold),
  ),
  onStepChanged: (index) => setState(() => _currentStep = index),
)

CoreStepperStyle Fields (all 17, source-verified)#

FieldTypeDescription
indicatorActiveColor CoreColor? Indicator fill for the current and completed steps
indicatorInactiveColor CoreColor? Indicator fill for pending steps
indicatorBorderColor CoreColor? Indicator border stroke colour (no border painted when both border fields are null)
indicatorBorderWidth double? Indicator border stroke width (logical px; falls back to CoreStrokeWidth.stroke1 when only the colour is set)
indicatorSize double? Indicator circle diameter (logical px; explicit override is used as-is, un-scaled)
connectorActiveColor CoreColor? Connector colour for completed segments
connectorInactiveColor CoreColor? Connector colour for pending segments
connectorThickness double? Connector line thickness (logical px)
indicatorContentGapStyle CoreGapStyle? Gap between indicator column and step content (vertical layout)
stepPadding CoreEdgeInsets? Padding below each non-final step (vertical layout)
connectorMargindouble?Margin around connectors
labelGapStyle CoreGapStyle? Gap between indicator and label (horizontal layout)
connectorMinExtent double? Minimum connector length in the vertical layout (logical px)
iconSizeRatio double? Check icon size relative to indicator diameter
labelStyle CoreTextStyle? Step label typography override
descriptionStyle CoreTextStyle? Step description typography override
indicatorIconStyle CoreIconStyle? Completed-step check icon style override

CoreStepperStyle also provides merge(other) and copyWith(...); nested labelStyle / descriptionStyle / indicatorIconStyle / gap slots merge recursively.

Design-system Defaults (source-verified statics on CoreStepperStyle)#

Static constantValue
defaultIndicatorSize CoreSpace.space32 (32.0, pre-scaling)
defaultConnectorThicknessCoreStrokeWidth.stroke2 (2.0)
defaultIndicatorContentGapStyle CoreGapStyle(size: CoreSpace.space12)
defaultStepPadding CoreEdgeInsets.only(bottom: CoreSpace.space24)
defaultConnectorMarginCoreSpace.space4 (4.0)
defaultLabelGapStyleCoreGapStyle(size: CoreSpace.space4)
defaultConnectorMinExtentCoreSpace.space8 (8.0)
defaultIconSizeRatio 0.5 (check icon = 50% of indicator diameter)
defaultIndicatorActiveColor CoreColor.token(CoreColors.primary)
defaultIndicatorInactiveColor CoreColor.token(CoreColors.surfaceContainer)
defaultConnectorActiveColor CoreColor.token(CoreColors.primary)
defaultConnectorInactiveColor CoreColor.token(CoreColors.surfaceContainer)
defaultIndicatorIconColor CoreColor.token(CoreColors.onPrimary)
defaultIndicatorNumberActiveStyle bodySmall role, bold, onPrimary
defaultIndicatorNumberInactiveStyle bodySmall role, bold, onSurface
defaultLabelVerticalStyle titleSmall role, onSurface
defaultLabelHorizontalStyle labelSmall role, onSurface
defaultDescriptionStyle bodySmall role, onSurfaceVariant

Project-level Theme#

Apply a shared style across all Stepper instances via CoreStepperTheme (the stepper slot on CoreComponentTheme):

// In your theme configuration:
coreComponentTheme: CoreComponentTheme(
  stepper: CoreStepperTheme(
    style: CoreStepperStyle(
      indicatorActiveColor: CoreColor.token(CoreColors.primary),
      connectorThickness: 2.0,
    ),
  ),
),

Multi-step Form Pattern#

import 'package:coui_flutter/coui_flutter.dart';
import 'package:flutter/widgets.dart';

class CheckoutStepper extends StatefulWidget {
  const CheckoutStepper({super.key});

  @override
  State<CheckoutStepper> createState() => _CheckoutStepperState();
}

class _CheckoutStepperState extends State<CheckoutStepper> {
  int _currentStep = 0;

  static const _steps = [
    CoreCoStepItem(label: 'Cart'),
    CoreCoStepItem(label: 'Shipping'),
    CoreCoStepItem(label: 'Payment'),
    CoreCoStepItem(label: 'Done'),
  ];

  @override
  Widget build(BuildContext context) {
    return Column(
      children: [
        Stepper(
          currentStep: _currentStep,
          steps: _steps,
          onStepChanged: (index) => setState(() => _currentStep = index),
        ),
        Expanded(child: _buildStepContent(_currentStep)),
        Row(
          mainAxisAlignment: MainAxisAlignment.spaceBetween,
          children: [
            if (_currentStep > 0)
              Button(
                onPressed: () => setState(() => _currentStep--),
                child: const Text('Back'),
              ),
            Button(
              onPressed: _currentStep < _steps.length - 1
                  ? () => setState(() => _currentStep++)
                  : null,
              child: Text(_currentStep < _steps.length - 1 ? 'Next' : 'Complete'),
            ),
          ],
        ),
      ],
    );
  }

  Widget _buildStepContent(int step) {
    // Swap in the form body for each step here.
    return Center(child: Text('Step ${step + 1} content'));
  }
}

Web (coui_web / Jaspr)#

Import#

import 'package:coui_web/coui_web.dart';

Horizontal Stepper#

Stepper(
  currentStep: currentStep,
  steps: const [
    CoreCoStepItem(label: 'Account'),
    CoreCoStepItem(label: 'Details'),
    CoreCoStepItem(label: 'Confirm'),
  ],
  onStepChanged: (index) => setState(() => currentStep = index),
)

Vertical Stepper#

Stepper(
  currentStep: currentStep,
  orientation: CoreStepperOrientation.vertical,
  steps: const [
    CoreCoStepItem(label: 'Create Account', description: 'Name and email'),
    CoreCoStepItem(label: 'Set Up Profile'),
    CoreCoStepItem(label: 'Complete'),
  ],
  onStepChanged: (index) => setState(() => currentStep = index),
)

Stepper Parameters (Web)#

The behaviour and style parameters are identical to Flutter (steps, currentStep, orientation, onStepChanged, stepperStyle). In addition, the Web Stepper extends UiComponent (rendered as a <div>) and accepts the standard Web chrome parameters:

Extra parameterTypeDescription
id String? DOM id on the root <div>
classesString?Extra CSS classes on the root
css Styles? Inline styles combined with the resolved root styles
attributes Map<String, String>? Extra HTML attributes
eventHandlers Map<String, List<UiEventHandler>>? Extra DOM event handlers

The Web Stepper also provides a copyWith(...) method covering all of the above plus the behaviour fields.

Web Accessibility Behaviour (source-verified)#

When onStepChanged is non-null, each indicator <div> gets role="button" and tabindex="0", and a click DOM event that calls onStepChanged(index). Only the click event is wired in v0.127.15 (no keydown handler).

Flutter โ†” Web Differences#

AspectFlutterWeb (Jaspr)
Import package:coui_flutter/coui_flutter.dart package:coui_web/coui_web.dart
Widget class Stepper (extends StatelessWidget) Stepper (extends UiComponent, renders <div>)
Shared contract CoreStepperContract CoreStepperContract (identical)
Step item type CoreCoStepItem CoreCoStepItem (identical)
Orientation enum CoreStepperOrientation CoreStepperOrientation (identical)
Callback onStepChanged: ValueChanged<int>? onStepChanged โ€” same name; contract type CoreValueChanged<int> (= void Function(int) )
Style slot / theme stepperStyle: CoreStepperStyle?, CoreStepperTheme identical
Completed-step icon Icon(LucideIcons.check, iconStyle: โ€ฆ) Icon(LucideIcons.check, iconStyle: โ€ฆ) (Web Icon component)
Click affordance MouseRegion + GestureDetector.onTap role="button" + tabindex="0" + click event
Extra chrome params none id , classes , css , attributes , eventHandlers ; plus copyWith(...)
Resolver output ResolvedStepper with paint-ready Color / TextStyle ResolvedStepper with WebElementChrome lists (Tailwind-style class names + inline CSS variables)
Scaling design defaults multiplied by theme.scaling ; explicit indicatorSize override used as-is no scaling step; values emitted as CSS px

The Stepper public API (parameter names, types, and every CoreStepperStyle field) is identical between Flutter and Web; the resolver internals differ but are transparent to callers.

v0.127 Migration (Legacy)#

v0.127.0 removed the Co prefix from widget classes โ€” a hard break with no aliases. โŒ marks legacy identifiers that no longer exist and must not appear in new code. Core* classes (CoreCoStepItem, CoreStepperOrientation, CoreStepperStyle, CoreStepperTheme, CoreGapStyle, CoreTextStyle, CoreIconStyle, โ€ฆ) are unchanged.

Legacy (โŒ removed in v0.127)v0.127 Canonical
โŒ CoStepperStepper
โŒ CoButtonButton
โŒ CoGapGap
โŒ CoIconIcon
โŒ CoDividerDivider
โŒ CoLucideIconsLucideIcons

The even older shadcn-style stepper API (a controller-based Stepper with StepperController, Step, StepVariant, StepSize) was already removed before v0.127 and does not exist in v0.127.15 โ€” the bare name Stepper now refers exclusively to the unified contract-based widget documented above.