HTML <progress> and <meter> reference Jump to this section

Common attributes Jump to this section

Basic structures Jump to this section

HTML provides two elements for displaying measurements: <progress> for tasks in progress (like file uploads or loading), and <meter> for scalar measurements within a known range (like disk usage or ratings).

Use <progress> for things that are actively changing. Use <meter> for measurements that exist at a point in time.

<!-- Progress: for tasks in progress -->
<progress value="70" max="100"></progress>
<!-- Meter: for measurements at a point in time -->
<meter value="70" min="0" max="100"></meter>

Progress attributes Jump to this section

value Jump to this section

Current value of the progress. Must be between 0 and max. If omitted, the progress bar is indeterminate (shows an animation).

Use value when you know the progress percentage. Omit it for loading states where the progress is unknown.

<!-- Determinate: known progress -->
<progress value="50" max="100"></progress>
<!-- Indeterminate: unknown progress -->
<progress></progress>

max Jump to this section

Maximum value for the progress bar. Defaults to 1.0 if not specified. The value attribute is relative to this maximum.

Use values that match your use case: 100 for percentages, file size for uploads, or number of steps for multi-step processes.

<!-- Step progress -->
<progress value="7" max="10"></progress>
<!-- Decimal progress -->
<progress value="0.75" max="1"></progress>
<!-- File upload (bytes) -->
<progress value="2048576" max="5242880"></progress>

Meter attributes Jump to this section

value Jump to this section

Current value being measured. Required attribute. Should be between min and max.

<meter value="6" min="0" max="10"></meter>

min Jump to this section

Minimum value of the range. Defaults to 0 if not specified.

Use when your measurement doesn't start at zero (like temperature scales or custom ranges).

<meter value="40" min="0" max="100"></meter>
<meter value="5" min="1" max="10"></meter>

max Jump to this section

Maximum value of the range. Defaults to 1.0 if not specified.

<meter value="75" min="0" max="100"></meter>

low Jump to this section

The upper boundary of the "low" range. Values at or below this are considered low. Used for semantic coloring.

When value is between min and low, many browsers color the meter yellow/orange to indicate a low reading.

<meter value="15" min="0" max="100" 
       low="20" high="80"></meter>

high Jump to this section

The lower boundary of the "high" range. Values at or above this are considered high.

When value is between high and max, browsers may use different coloring to indicate a high reading.

<meter value="92" min="0" max="100" 
       low="20" high="80"></meter>

optimum Jump to this section

The optimal value within the range. Helps browsers determine the meter's semantic meaning and coloring.

  • If optimum is between min and low: lower values are better (green when low, red when high)
  • If optimum is between low and high: middle values are better (green in middle, yellow/red at extremes)
  • If optimum is between high and max: higher values are better (green when high, red when low)
<!-- Higher is better (battery) -->
<meter value="80" min="0" max="100" 
       low="20" high="80" optimum="100"></meter>
<!-- Middle is better (temperature) -->
<meter value="22" min="0" max="40" 
       low="15" high="25" optimum="20"></meter>

Common use cases Jump to this section

Progress use cases Jump to this section

<!-- File upload progress -->
<label>Uploading file: <span id="percent">0%</span></label>
<progress id="upload" value="0" max="100"></progress>
<!-- Multi-step form -->
<label>Step 3 of 5</label>
<progress value="3" max="5"></progress>
<!-- Loading indicator (indeterminate) -->
<label>Loading...</label>
<progress></progress>
<!-- Download progress -->
<label>Downloading: 2.5 MB of 10 MB</label>
<progress value="2.5" max="10"></progress>

Meter use cases Jump to this section

<!-- Disk usage -->
<label>Disk usage: 75 GB of 100 GB</label>
<meter value="75" min="0" max="100" low="50" high="80"></meter>
<!-- Battery level -->
<label>Battery: 85%</label>
<meter value="85" min="0" max="100" low="20" high="80" optimum="100"></meter>
<!-- Rating score -->
<label>Review rating: 4.2 out of 5</label>
<meter value="4.2" min="0" max="5" low="2" high="4" optimum="5"></meter>
<!-- Temperature -->
<label>CPU Temperature: 65°C</label>
<meter value="65" min="0" max="100" low="40" high="80" optimum="50"></meter>
<!-- Relevance/similarity score -->
<label>Match: 78%</label>
<meter value="0.78" max="1" low="0.3" high="0.7" optimum="1"></meter>

Accessibility attributes Jump to this section

aria-label Jump to this section

Provides an accessible label when a visible <label> element isn't present.

<progress value="60" max="100" aria-label="Upload progress: 60%"></progress>
<meter value="75" max="100" aria-label="Disk usage: 75%"></meter>

aria-labelledby Jump to this section

References a label element by its id.

<span id="upload-label">File upload progress</span>
<progress value="45" max="100" aria-labelledby="upload-label"></progress>

aria-describedby Jump to this section

References descriptive text to provide additional context.

<progress value="50" max="100" aria-describedby="upload-desc"></progress>
<p id="upload-desc">Uploading profile picture...</p>

aria-valuenow, aria-valuemin, aria-valuemax Jump to this section

These are automatically set by browsers for <progress> and <meter>, but can be explicitly set if needed.

<progress value="50" max="100" 
          aria-valuenow="50" 
          aria-valuemin="0" 
          aria-valuemax="100"></progress>

role Jump to this section

Both elements have implicit roles (progressbar for progress, meter for meter). Generally don't need to be overridden.

<progress role="progressbar" value="50" max="100"></progress>
<meter role="meter" value="50" max="100"></meter>

Common attributes Jump to this section

id Jump to this section

Unique identifier for the element. Essential for JavaScript manipulation and label association.

<label for="file-progress">Upload progress:</label>
<progress id="file-progress" value="0" max="100"></progress>
<script>
  const progress = document.getElementById('file-progress');
  progress.value = 75;
</script>

class Jump to this section

Assigns CSS class names for styling.

<progress class="progress-large progress-primary" value="60" max="100"></progress>
<meter class="meter-success" value="80" max="100"></meter>

data-* attributes Jump to this section

Custom attributes for storing application-specific data.

<progress value="50" max="100" 
          data-upload-id="abc123" 
          data-file-name="document.pdf"></progress>
<meter value="75" max="100" 
       data-metric="disk-usage" 
       data-threshold="warning"></meter>
<script>
  const progress = document.querySelector('progress');
  console.log(progress.dataset.uploadId);   // "abc123"
  console.log(progress.dataset.fileName);   // "document.pdf"
</script>

title Jump to this section

Provides tooltip text on hover. Not accessible to keyboard-only users or touch devices.

<progress value="75" max="100" title="Upload is 75% complete"></progress>
<meter value="65" max="100" title="Battery level: 65%"></meter>

Fallback content Jump to this section

Both elements support fallback content for browsers that don't support them (rare nowadays). The content between tags is only shown in unsupported browsers.

<progress value="70" max="100">
    <span>70%</span>
</progress>
<meter value="6" min="0" max="10">
    6 out of 10
</meter>

Styling basics Jump to this section

Progress and meter elements are notoriously difficult to style consistently across browsers. Each browser uses different pseudo-elements for styling, and appearance varies significantly.

Basic styling Jump to this section

progress, meter {
    width: 250px;
    height: 20px;
}
/* Note: Default styling varies by browser */

Reset default appearance Jump to this section

Remove browser-specific styling to apply custom styles.

progress {
    appearance: none;
    width: 250px;
    height: 12px;
    border: none;
    background: #e5e7eb;
    border-radius: 10px;
    overflow: hidden;
}
/* WebKit (Chrome, Safari, Edge) */
progress::-webkit-progress-bar {
    background: #e5e7eb;
    border-radius: 10px;
}
progress::-webkit-progress-value {
    background: #3b82f6;
    border-radius: 10px;
}
/* Firefox */
progress::-moz-progress-bar {
    background: #3b82f6;
    border-radius: 10px;
}

Progress bar styles Jump to this section

Modern rounded progress Jump to this section

.progress-modern {
    appearance: none;
    width: 250px;
    height: 8px;
    border: none;
    background: #f3f4f6;
    border-radius: 10px;
}
.progress-modern::-webkit-progress-bar {
    background: #f3f4f6;
    border-radius: 10px;
}
.progress-modern::-webkit-progress-value {
    background: #3b82f6;
    border-radius: 10px;
}
.progress-modern::-moz-progress-bar {
    background: #3b82f6;
    border-radius: 10px;
}

Thick progress bar Jump to this section

.progress-thick {
    appearance: none;
    width: 250px;
    height: 24px;
    border: none;
    background: #e5e7eb;
    border-radius: 12px;
}
.progress-thick::-webkit-progress-value {
    background: linear-gradient(90deg, #3b82f6, #2563eb);
    border-radius: 12px;
}
.progress-thick::-moz-progress-bar {
    background: linear-gradient(90deg, #3b82f6, #2563eb);
    border-radius: 12px;
}

Striped progress Jump to this section

.progress-striped::-webkit-progress-value {
    background: linear-gradient(
        45deg,
        rgba(255,255,255,.15) 25%,
        transparent 25%,
        transparent 50%,
        rgba(255,255,255,.15) 50%,
        rgba(255,255,255,.15) 75%,
        transparent 75%,
        transparent
    ), #3b82f6;
    background-size: 20px 20px;
    border-radius: 10px;
}

Gradient progress Jump to this section

.progress-gradient::-webkit-progress-value {
    background: linear-gradient(
        90deg, 
        #06b6d4, 
        #3b82f6, 
        #8b5cf6
    );
    border-radius: 8px;
}

Progress with shadow Jump to this section

.progress-shadow {
    appearance: none;
    background: #f3f4f6;
    border-radius: 10px;
    box-shadow: inset 0 2px 4px rgba(0,0,0,0.1);
}
.progress-shadow::-webkit-progress-value {
    background: #10b981;
    border-radius: 10px;
    box-shadow: 0 2px 4px rgba(16, 185, 129, 0.4);
}

Color variants Jump to this section

.progress-success::-webkit-progress-value {
    background: #10b981; /* Green */
}
.progress-warning::-webkit-progress-value {
    background: #f59e0b; /* Orange */
}
.progress-danger::-webkit-progress-value {
    background: #ef4444; /* Red */
}

Meter styles Jump to this section

Custom meter styling Jump to this section

.meter-custom {
    appearance: none;
    width: 250px;
    height: 16px;
    background: #e5e7eb;
    border-radius: 8px;
}
.meter-custom::-webkit-meter-optimum-value {
    background: #10b981; /* Green - good */
    border-radius: 8px;
}
.meter-custom::-webkit-meter-suboptimum-value {
    background: #f59e0b; /* Orange - warning */
    border-radius: 8px;
}
.meter-custom::-webkit-meter-even-less-good-value {
    background: #ef4444; /* Red - bad */
    border-radius: 8px;
}

Meter with border Jump to this section

.meter-border {
    appearance: none;
    width: 250px;
    height: 20px;
    background: white;
    border: 2px solid #d1d5db;
    border-radius: 10px;
}

Circular progress (CSS only) Jump to this section

65%
.circular-progress {
    width: 80px;
    height: 80px;
    border-radius: 50%;
    background: conic-gradient(
        #3b82f6 0% 65%, 
        #e5e7eb 65% 100%
    );
    display: flex;
    align-items: center;
    justify-content: center;
    position: relative;
}
.circular-progress::before {
    content: '';
    width: 60px;
    height: 60px;
    border-radius: 50%;
    background: white;
    position: absolute;
}
.circular-progress-text {
    position: relative;
    z-index: 1;
    font-weight: 600;
}

Indeterminate animation Jump to this section

@keyframes indeterminate {
    0% { transform: translateX(-100%); }
    100% { transform: translateX(400%); }
}
.progress-indeterminate::-webkit-progress-value {
    background: #3b82f6;
    animation: indeterminate 1.5s infinite ease-in-out;
}

With label overlay Jump to this section

72%
.progress-container {
    position: relative;
}
.progress-label {
    position: absolute;
    top: 50%;
    left: 50%;
    transform: translate(-50%, -50%);
    font-size: 13px;
    font-weight: 600;
    color: white;
    mix-blend-mode: difference;
}

JavaScript patterns Jump to this section

Progress and meter elements are commonly manipulated with JavaScript to reflect real-time updates, track completion, and provide visual feedback.

Update progress value Jump to this section

Change the progress value dynamically.

const progress = document.getElementById('my-progress');
// Set value directly
progress.value = 50;
// Increment
progress.value += 10;
// Decrement
progress.value -= 10;
// Reset
progress.value = 0;
// Set to maximum
progress.value = progress.max;

Animate progress Jump to this section

Smoothly animate progress from one value to another.

function animateProgress(elementId, targetValue, duration) {
    const progress = document.getElementById(elementId);
    const startValue = progress.value;
    const difference = targetValue - startValue;
    const startTime = performance.now();
    function update(currentTime) {
        const elapsed = currentTime - startTime;
        const progress_ratio = Math.min(elapsed / duration, 1);
        progress.value = startValue + (difference * progress_ratio);
        if (progress_ratio < 1) {
            requestAnimationFrame(update);
        }
    }
    requestAnimationFrame(update);
}
// Usage
animateProgress('my-progress', 75, 1000); // To 75% in 1 second

Calculate percentage Jump to this section

Calculate and display the percentage from progress value.

67%
const progress = document.getElementById('my-progress');
const display = document.getElementById('percent-display');
function updatePercentage() {
    const percentage = (progress.value / progress.max) * 100;
    display.textContent = Math.round(percentage) + '%';
}
// Update on value change
progress.value = 67;
updatePercentage(); // "67%"

Simulate file upload Jump to this section

Create a realistic file upload progress simulation.

Uploading: document.pdf
Ready to upload
function simulateUpload() {
    const progress = document.getElementById('upload-progress');
    const status = document.getElementById('upload-status');
    progress.value = 0;
    status.textContent = 'Uploading...';
    const interval = setInterval(() => {
        progress.value += Math.random() * 10;
        if (progress.value >= 100) {
            progress.value = 100;
            clearInterval(interval);
            status.textContent = 'Upload complete!';
            status.style.color = '#10b981';
        } else {
            const percent = Math.round(progress.value);
            status.textContent = `Uploading... ${percent}%`;
        }
    }, 200);
}
// With fetch API (real upload)
async function uploadFile(file) {
    const formData = new FormData();
    formData.append('file', file);
    const xhr = new XMLHttpRequest();
    xhr.upload.addEventListener('progress', (e) => {
        if (e.lengthComputable) {
            const percent = (e.loaded / e.total) * 100;
            progress.value = percent;
        }
    });
    xhr.open('POST', '/upload');
    xhr.send(formData);
}

Multi-step progress Jump to this section

Track progress through multiple steps.

Step 1 of 4
const progress = document.getElementById('step-progress');
const label = document.getElementById('step-label');
let currentStep = 1;
const totalSteps = 4;
function updateStep(direction) {
    currentStep = Math.max(1, Math.min(totalSteps, currentStep + direction));
    progress.value = currentStep;
    label.textContent = `Step ${currentStep} of ${totalSteps}`;
    // Optional: Show different content per step
    showStepContent(currentStep);
}
function showStepContent(step) {
    // Hide all steps
    document.querySelectorAll('.step-content').forEach(el => {
        el.style.display = 'none';
    });
    // Show current step
    document.getElementById(`step-${step}`).style.display = 'block';
}

Update meter dynamically Jump to this section

Change meter values and thresholds dynamically.

45 GB of 100 GB used
const meter = document.getElementById('disk-usage');
const display = document.getElementById('usage-display');
// Update value
meter.value = 75;
display.textContent = `${meter.value} GB of ${meter.max} GB used`;
// Update thresholds
meter.low = 40;
meter.high = 80;
meter.optimum = 20;
// Dynamic updates (e.g., from API)
async function updateDiskUsage() {
    const response = await fetch('/api/disk-usage');
    const data = await response.json();
    meter.value = data.used;
    meter.max = data.total;
    display.textContent = `${data.used} GB of ${data.total} GB used`;
}

Monitor progress completion Jump to this section

Detect when progress reaches 100% and trigger actions.

const progress = document.getElementById('complete-progress');
const status = document.getElementById('complete-status');
function startProgress() {
    progress.value = 0;
    status.textContent = 'In progress...';
    status.style.color = '#6b7280';
    const interval = setInterval(() => {
        progress.value += 5;
        if (progress.value >= 100) {
            clearInterval(interval);
            onComplete();
        }
    }, 100);
}
function onComplete() {
    status.textContent = '✓ Complete!';
    status.style.color = '#10b981';
    // Trigger additional actions
    showSuccessMessage();
    enableNextButton();
    logCompletion();
}

Get progress percentage Jump to this section

Read the current progress as a percentage.

Progress: 42%
Remaining: 58%
Status: In progress
const progress = document.getElementById('my-progress');
// Calculate percentage
const percentage = (progress.value / progress.max) * 100;
console.log(`Progress: ${percentage}%`);
// Check if complete
if (percentage >= 100) {
    console.log('Task completed!');
}
// Check if half complete
if (percentage >= 50) {
    console.log('Halfway there!');
}
// Get remaining percentage
const remaining = 100 - percentage;
console.log(`${remaining}% remaining`);

Progress with time estimate Jump to this section

Calculate and display estimated time remaining.

Calculating...
function startTimedProgress() {
    const progress = document.getElementById('time-progress');
    const estimate = document.getElementById('time-estimate');
    const startTime = Date.now();
    let lastUpdate = startTime;
    const interval = setInterval(() => {
        progress.value += 2;
        const now = Date.now();
        // Calculate time remaining
        const elapsed = (now - startTime) / 1000; // seconds
        const rate = progress.value / elapsed; // units per second
        const remaining = progress.max - progress.value;
        const timeRemaining = remaining / rate;
        estimate.textContent = `${Math.round(timeRemaining)}s remaining`;
        if (progress.value >= 100) {
            clearInterval(interval);
            estimate.textContent = 'Complete!';
        }
    }, 200);
}

Pause and resume progress Jump to this section

Control progress execution with pause/resume functionality.

let progressInterval = null;
let isPaused = true;
function togglePause() {
    const progress = document.getElementById('pause-progress');
    const btn = document.getElementById('pause-btn');
    if (isPaused) {
        // Start/Resume
        progressInterval = setInterval(() => {
            progress.value = Math.min(100, progress.value + 2);
            if (progress.value >= 100) {
                clearInterval(progressInterval);
                btn.textContent = 'Start';
                isPaused = true;
            }
        }, 100);
        btn.textContent = 'Pause';
        isPaused = false;
    } else {
        // Pause
        clearInterval(progressInterval);
        btn.textContent = 'Resume';
        isPaused = true;
    }
}
function resetProgress() {
    clearInterval(progressInterval);
    document.getElementById('pause-progress').value = 0;
    document.getElementById('pause-btn').textContent = 'Start';
    isPaused = true;
}

Multiple progress bars Jump to this section

Manage and update multiple progress bars simultaneously.

Download 1
Download 2
Download 3
Ready
const progressBars = [
    { id: 'download1', rate: 5 },
    { id: 'download2', rate: 3 },
    { id: 'download3', rate: 7 }
];
function updateAllProgress() {
    progressBars.forEach(bar => {
        const element = document.getElementById(bar.id);
        element.value += bar.rate;
        if (element.value >= element.max) {
            element.value = element.max;
        }
    });
}
// Update every 200ms
let updateInterval = setInterval(updateAllProgress, 200);
// Check if all complete
function allComplete() {
    return progressBars.every(bar => {
        const element = document.getElementById(bar.id);
        return element.value >= element.max;
    });
}