Patterns
Carousel Indicators
When to use:
- a short carousel or hero sequence needs compact navigation
Composition notes:
- keep the sequence short
- sync active state with the actual slide
Trade-offs:
- compact and familiar
- not informative enough for long sequences
import { Card, Heading, PageControl, PageControlItem, Text } from '@vega-ui/react';
import { useId, useState } from 'react';
const slides = [
{ title: 'Plan your week', text: 'Drag tasks onto the calendar and see your free time at a glance.' },
{ title: 'Share with your team', text: 'Invite teammates to a board and assign tasks in one click.' },
{ title: 'Stay on track', text: 'Get a summary every Monday with what slipped and what is next.' },
];
export default function CarouselIndicators() {
const prefix = useId();
const [active, setActive] = useState(0);Timed Progress Dots
When to use:
- autoplay or timed advancement is part of the experience
Composition notes:
- use
PageControlProgress - keep duration aligned with the real autoplay timing
Trade-offs:
- communicates progression visually
- adds temporal complexity and potential accessibility risks
import { Button, PageControl, PageControlItem, PageControlProgress, Text } from '@vega-ui/react';
import { useState } from 'react';
const stories = ['New arrivals', 'Autumn sale', 'Gift cards', 'Store opening'];
export default function TimedProgressIndicators() {
const [active, setActive] = useState(0);
const [paused, setPaused] = useState(false);
const advance = () => setActive((active + 1) % stories.length);
return (Step Indicators Above A Form
When to use:
- a multi-step flow needs lightweight progress cues
Composition notes:
- keep step semantics elsewhere if labels matter
- use
activeto reflect the real current step
Trade-offs:
- compact
- less explanatory than a labeled stepper
import { Button, HelperText, Label, PageControl, PageControlItem, Text, TextField, TextFieldInput } from '@vega-ui/react';
import { FormEvent, MouseEvent, useId, useState } from 'react';
const steps = ['Account', 'Workspace', 'Invite'];
export default function StepIndicatorAboveFormPages() {
const id = useId();
const [step, setStep] = useState(0);
const [reached, setReached] = useState(0);
const [result, setResult] = useState('');
const goTo = (index: number) => {Comparison
Quick Decision Rule
Use PageControl for short sequences like onboarding steps or carousels. Use Pagination for real page navigation across result sets.
PageControl vs Pagination
- Use
PageControlfor short, compact sequences. - Use
Paginationfor explicit page numbers and dataset navigation.
PageControl vs Stepper
- Use
PageControlwhen indicators are intentionally minimal. - Use a fuller stepper pattern when step labels and richer status matter.
Choose This Component When
- the sequence is short
- compact indicators are enough
Do Not Choose This Component When
- users need page numbers or routeable destinations
- each step requires visible labels and richer state
Troubleshooting
The Active Dot Does Not Match The Real Carousel Or Step
Symptom
The visible active indicator and the actual content state drift apart.
Likely Cause
The parent feature and PageControl are not sharing one source of truth.
How To Verify
- compare the current content index with the rendered
activeitem
Fix
Drive both the content and PageControl from the same parent state.
Progress Dots Animate But Nothing Advances
Symptom
The progress fill animates, but the content never changes.
Likely Cause
PageControlProgress was rendered without matching step-advance logic in the parent flow.
How To Verify
- inspect whether the parent updates active state on animation end or autoplay tick
Fix
Connect progress completion to the actual advancement logic or switch to static items.
Long Sequences Feel Unreadable
Symptom
The row of indicators becomes hard to count or understand.
Likely Cause
PageControl is being used for too many items.
How To Verify
- count the visible items and compare with actual user comprehension
Fix
Switch to Pagination, a labeled stepper, or another richer navigation pattern.