Tessina UI
Tessina UI
GitHubIntroduction
InstallationUsageTheming
Components
ButtonIconButtonLinkSpinnerBadgeAvatarStatusChipShortcutSkeletonSurfaceProgressMeterRating
LabelFieldFieldsetCheckboxRadioSwitchSliderSelectComboboxSearchTextareaNumberFieldDate PickerTime PickerOTP InputFile UploadCalendar
AlertBannerToastTooltip
TabsAccordionCollapsibleBreadcrumbPaginationStepperSegmentedControlButtonGroupToggleButtonToggleGroupToolbarNavigation MenuMenubarBottom NavSidebar
Split ButtonFABDropdown MenuContextMenuCommandPopoverHoverCard
ContainerStackFlexGridAspectRatioSpacerCardCarouselDividerScroll Area
Table
ChatBubblePromptInputCodeBlock
ModalAlertDialogDrawerAction SheetTop Header MobileTop Header DesktopEmptyStateForm
Contributing
ComponentsNavigation Blocks

Stepper

Numeric quantity control with increment and decrement buttons. Two layout variants (pill and floating), five sizes, full intent palette, min/max/step, disabled/readOnly states, and LTR/RTL support.

Playground

Installation

pnpm add @tessinaui/ui

Usage

import { Stepper } from "@tessinaui/ui";
{/* Basic uncontrolled */}
<Stepper />

{/* Controlled */}
<Stepper value={quantity} onChange={setQuantity} min={1} max={99} />

{/* Floating variant */}
<Stepper variant="floating" />

{/* With label + validation */}
<Stepper
  label="Quantity"
  intent="error"
  supportingText="Maximum quantity reached"
  value={10}
  min={1}
  max={10}
/>

{/* Custom step */}
<Stepper step={5} defaultValue={0} />

{/* RTL */}
<Stepper dir="rtl" label="الكمية" />

Showcase

API Reference

Props

PropTypeDefaultDescription
variant"pill" | "floating""pill"pill — decrement, value and increment share one rounded-full container; floating — each button is a separate filled circle
size"xs" | "sm" | "md" | "lg" | "xl""md"Size token
intent"none" | "primary" | "error" | "warning" | "success" | "info""none"Color intent — affects button/pill background and text color
valuenumber—Controlled value
defaultValuenumber0Uncontrolled initial value
minnumber-InfinityMinimum value — decrement button is disabled at this value
maxnumberInfinityMaximum value — increment button is disabled at this value
stepnumber1Amount to change per click
disabledbooleanfalseDisable all interaction; container gets opacity-60
readOnlybooleanfalseButtons are non-interactive (opacity-40, pointer-events-none) — value is still visible
onChange(value: number) => void—Called with the new value after each change
labelstring—Visible label rendered above the stepper
supportingTextstring—Helper / validation text below the stepper (colored by intent)
groupLabelstring"Quantity"aria-label for the stepper group — used when no visible label is provided
decrementLabelstring"Decrease"aria-label for the − button
incrementLabelstring"Increase"aria-label for the + button
dir"ltr" | "rtl""ltr"Text direction. In RTL the + button is on the leading (right) side and − is on the trailing (left) side
rounded"none" | "sm" | "md" | "lg" | "full""full"Border radius of the pill container and pill buttons. Floating buttons always stay rounded-full.
classNamestring—Additional class on the root wrapper

Variants

VariantDescription
pillAll three elements (−, value, +) are inside one rounded-full container. Buttons are transparent; the pill provides the background.
floatingEach button is a standalone filled circle. The value floats between them without a container background.

Intents

IntentPill bgFloating button bgText
nonebg-secondarybg-secondarytext-foreground
primarybg-primarybg-primarytext-on-primary
errorbg-error-lightbg-error-lighttext-error
warningbg-warning-lightbg-warning-lighttext-warning
successbg-success-lightbg-success-lighttext-success
infobg-info-lightbg-info-lighttext-info

Notes

  • Controlled vs uncontrolled: When value is provided, the component is fully controlled. Use defaultValue for uncontrolled usage.
  • Min/Max clamping: Values are always clamped between min and max. The respective button dims to opacity-30 and becomes non-interactive at the boundary.
  • RTL: dir="rtl" swaps the + and − button sides. The + button moves to the inline-start (right) side, matching standard RTL number-input conventions (e.g. Carbon, Fluent).
  • Accessibility: The control renders as role="group" with aria-label or aria-labelledby. The value uses <output aria-live="polite"> so screen readers announce changes.
  • readOnly vs disabled: disabled prevents all interaction and dims the whole control; readOnly dims only the buttons (the value remains clearly readable).

Pagination

Navigate between pages of content. Five sizes, three active-page variants (default/primary/outline), five border-radius options (none/sm/md/lg/full), three nav styles (icon/label/none), smart ellipsis truncation, controlled/uncontrolled, and LTR/RTL support.

SegmentedControl

A radio-group style control where one segment is selected at a time, with outline, filled, and pill variants, horizontal and vertical orientations, and full keyboard navigation

On this page

PlaygroundInstallationUsageShowcaseAPI ReferencePropsVariantsIntentsNotes