Skip to content

Progress ​

Display progress with elegant linear bars, circular indicators, or gauge visualizations. Perfect for loading states, file uploads, and goal tracking. Built for Pulse Framework with full reactivity support.

Import ​

tsx
import { Progress, Pulse } from '@odyssee-software/components';

Basic Usage ​

Code Éditable
Résultat

With Label and Value ​

Display progress with descriptive labels and percentage values.

Code Éditable
Résultat

Sizes ​

Five size options control the height of linear progress bars: xs, sm, md, lg, and xl.

Code Éditable
Résultat

Colors ​

Progress bars support multiple color schemes: primary, secondary, success, danger, warning, info, light, and dark.

Code Éditable
Résultat

Value Positions ​

Control where the percentage value is displayed.

Code Éditable
Résultat

Segmented Progress ​

Display progress as discrete steps or segments.

Code Éditable
Résultat

Circular Progress ​

Display progress in a circular format.

Code Éditable
Résultat

Gauge Progress ​

Visualize progress as a gauge or speedometer.

Code Éditable
Résultat

Vertical Progress ​

Display progress bars vertically.

tsx
const verticalProgress = (
  <div class="flex gap-4 h-48">
    <Progress value={25} vertical={true} />
    <Progress value={50} vertical={true} color="success" />
    <Progress value={75} vertical={true} color="warning" />
    <Progress value={100} vertical={true} color="danger" />
  </div>
);

Reactive Progress ​

Use Pulse signals for dynamic progress updates.

tsx
import { Progress, Button, Pulse } from '@odyssee-software/components';

const FileUploader = () => {
  const uploadProgress = Pulse.signal(0);
  const isUploading = Pulse.signal(false);

  const simulateUpload = () => {
    isUploading(true);
    uploadProgress(0);

    const interval = setInterval(() => {
      uploadProgress(prev => {
        if (prev >= 100) {
          clearInterval(interval);
          isUploading(false);
          return 100;
        }
        return prev + 10;
      });
    }, 300);
  };

  return (
    <div class="space-y-4">
      <Progress
        value={uploadProgress}
        label="Uploading file..."
        showValue={true}
        color="primary"
        transition={true}
      />
      <Button
        onClick={simulateUpload}
        disabled={isUploading}
      >
        {isUploading() ? 'Uploading...' : 'Start Upload'}
      </Button>
    </div>
  );
};

Custom Value Format ​

Format the displayed value with a custom function.

tsx
const downloadProgress = (
  <Progress
    value={50}
    max={1000}
    showValue={true}
    valueFormat={(value, max) => `${value}/${max} MB`}
    label="Downloading..."
  />
);

const timeProgress = (
  <Progress
    value={300}
    max={600}
    showValue={true}
    valueFormat={(value, max) => {
      const minutes = Math.floor(value / 60);
      const seconds = value % 60;
      return `${minutes}:${seconds.toString().padStart(2, '0')}`;
    }}
    label="Time Remaining"
  />
);

With Status Icon ​

Add a status icon to indicate completion or state.

tsx
const progressWithStatus = (
  <Progress
    value={100}
    color="success"
    showValue={true}
    valuePosition="end"
    showStatus={true}
    statusIcon="✓"
  />
);

Animated Progress ​

Enable smooth transitions for value changes.

tsx
const animatedProgress = Pulse.signal(0);

// Animate to 100%
setTimeout(() => animatedProgress(100), 100);

const progress = (
  <Progress
    value={animatedProgress}
    transition={true}
    showValue={true}
  />
);

Complete Example ​

Here's a comprehensive multi-step form with progress tracking:

tsx
import { Progress, Button, Card, Badge, Pulse } from '@odyssee-software/components';

const MultiStepForm = () => {
  const currentStep = Pulse.signal(1);
  const totalSteps = 4;

  const steps = [
    { id: 1, title: 'Personal Info', icon: '👤' },
    { id: 2, title: 'Address', icon: '📍' },
    { id: 3, title: 'Payment', icon: '💳' },
    { id: 4, title: 'Review', icon: '✓' }
  ];

  const progress = Pulse.computed(() => 
    (currentStep() / totalSteps) * 100
  );

  const nextStep = () => {
    if (currentStep() < totalSteps) {
      currentStep(currentStep() + 1);
    }
  };

  const prevStep = () => {
    if (currentStep() > 1) {
      currentStep(currentStep() - 1);
    }
  };

  return (
    <Card size="lg">
      {/* Progress bar */}
      <Progress
        value={progress}
        segments={totalSteps}
        color="primary"
        showValue={true}
        valuePosition="end"
        transition={true}
      />

      {/* Step indicators */}
      <div class="flex justify-between mt-6 mb-8">
        {steps.map(step => (
          <div class="flex flex-col items-center">
            <div class={`
              w-10 h-10 rounded-full flex items-center justify-center
              ${currentStep() >= step.id 
                ? 'bg-blue-600 text-white' 
                : 'bg-gray-200 text-gray-500'}
            `}>
              {step.icon}
            </div>
            <span class={`
              text-xs mt-2
              ${currentStep() >= step.id 
                ? 'text-blue-600 font-semibold' 
                : 'text-gray-500'}
            `}>
              {step.title}
            </span>
          </div>
        ))}
      </div>

      {/* Step content */}
      <div class="min-h-48 mb-6">
        <h3 class="text-xl font-bold mb-4">
          Step {currentStep()}: {steps[currentStep() - 1].title}
        </h3>
        <p class="text-gray-600">
          Form content for step {currentStep()} goes here...
        </p>
      </div>

      {/* Navigation buttons */}
      <div class="flex justify-between">
        <Button
          variant="outline"
          onClick={prevStep}
          disabled={currentStep() === 1}
        >
          Previous
        </Button>
        <Button
          variant="solid"
          color="primary"
          onClick={nextStep}
          disabled={currentStep() === totalSteps}
        >
          {currentStep() === totalSteps ? 'Complete' : 'Next'}
        </Button>
      </div>
    </Card>
  );
};

Props ​

PropTypeDefaultDescription
valuenumber | Signal<number>RequiredCurrent progress value
maxnumber100Maximum value
minnumber0Minimum value
labelstring-Progress label text
showValuebooleanfalseShow percentage/value
valuePosition"inside" | "end" | "top" | "floating""inside"Value display position
valueFormat(value: number, max: number) => string-Custom value formatter
size"xs" | "sm" | "md" | "lg" | "xl""md"Progress bar size
color"primary" | "secondary" | "success" | "danger" | "warning" | "info" | "light" | "dark""primary"Color scheme
roundedbooleantrueRounded corners
verticalbooleanfalseVertical orientation
heightstring-Custom height (vertical mode)
segmentsnumber-Number of discrete segments
segmentGapstring"gap-x-1"Gap between segments
type"linear" | "circular" | "gauge" | "gauge-half""linear"Progress display type
circularSizenumber160Size for circular/gauge
strokeWidthnumber2Stroke width for circular
transitionbooleantrueSmooth transitions
showStatusbooleanfalseShow status indicator
statusIconstring | HTMLElement-Status icon content
classNamestring-Additional CSS classes
idstring-HTML id attribute

Accessibility ​

The Progress component follows accessibility best practices:

  • ✅ Uses proper role="progressbar" attribute
  • ✅ Includes aria-valuenow, aria-valuemin, aria-valuemax
  • ✅ Provides descriptive labels
  • ✅ Screen reader friendly
  • ✅ Sufficient color contrast

ARIA Attributes ​

tsx
const accessibleProgress = (
  <Progress
    value={50}
    label="File upload progress"
    aria-label="Uploading document.pdf"
  />
);

Best Practices ​

✅ Do ​

  • Always provide labels for context
  • Use appropriate colors (success for completion, danger for errors)
  • Update progress smoothly with transitions
  • Show percentage for clarity
  • Use segments for step-based processes
tsx
// Good: Clear label and context
const goodProgress = (
  <Progress
    value={uploadProgress}
    label="Uploading document.pdf"
    showValue={true}
    color="primary"
    transition={true}
  />
);

❌ Don't ​

  • Don't use progress bars for indefinite waits (use spinners)
  • Don't update too frequently (causes jitter)
  • Don't forget to show completion state
  • Don't use tiny progress bars (hard to see)
tsx
// Bad: No context, no value display
const badProgress = (
  <Progress value={someValue} />
);

// Better: Clear and informative
const betterProgress = (
  <Progress
    value={someValue}
    label="Processing..."
    showValue={true}
    color="primary"
  />
);

Use Cases ​

File Upload ​

tsx
const FileUpload = ({ file, progress }) => (
  <div class="space-y-2">
    <div class="flex justify-between">
      <span class="text-sm font-medium">{file.name}</span>
      <span class="text-xs text-gray-500">
        {Math.round(progress() * file.size / 100)} / {file.size} KB
      </span>
    </div>
    <Progress
      value={progress}
      color="success"
      showValue={true}
      valuePosition="end"
    />
  </div>
);

Goal Tracking ​

tsx
const GoalTracker = ({ goal, current }) => {
  const percentage = (current / goal) * 100;
  
  return (
    <Card>
      <h3 class="font-bold mb-2">Monthly Sales Goal</h3>
      <Progress
        value={current}
        max={goal}
        type="gauge"
        label="Sales"
        showValue={true}
        valueFormat={(val, max) => `$${val.toLocaleString()} / $${max.toLocaleString()}`}
        color={percentage >= 100 ? 'success' : percentage >= 75 ? 'warning' : 'primary'}
      />
    </Card>
  );
};

Loading State ​

tsx
const LoadingScreen = ({ stage }) => {
  const stages = [
    'Initializing...',
    'Loading resources...',
    'Processing data...',
    'Almost ready...'
  ];

  const progress = ((stage + 1) / stages.length) * 100;

  return (
    <div class="flex flex-col items-center justify-center h-screen">
      <div class="w-96">
        <Progress
          value={progress}
          label={stages[stage]}
          showValue={true}
          color="primary"
          transition={true}
        />
      </div>
    </div>
  );
};

Skill Levels ​

tsx
const SkillLevels = ({ skills }) => (
  <div class="space-y-4">
    {skills.map(skill => (
      <div>
        <div class="flex justify-between mb-1">
          <span class="text-sm font-medium">{skill.name}</span>
          <span class="text-sm text-gray-500">{skill.level}%</span>
        </div>
        <Progress
          value={skill.level}
          color={skill.level >= 80 ? 'success' : skill.level >= 50 ? 'primary' : 'warning'}
          size="sm"
        />
      </div>
    ))}
  </div>
);

Styling & Theming ​

All progress styles use Tailwind CSS classes and support dark mode automatically.

Custom Styling ​

tsx
const customProgress = (
  <Progress
    value={75}
    className="shadow-lg"
    color="primary"
  />
);

TypeScript ​

Full TypeScript support with complete type definitions:

tsx
import type { ProgressProps } from '@odyssee-software/components';

const props: ProgressProps = {
  value: 50,
  max: 100,
  showValue: true,
  label: 'Progress',
  color: 'primary',
  type: 'linear'
};

const progress = <Progress {...props} />;
  • Spinner - For indefinite loading
  • Badge - For status indicators
  • Card - Wrap progress in cards

Version: 1.0.0
Last Updated: January 2025

Released under the MIT License.