ScreenshotNeo

BlogHow-to

How to Create a Bar Graph in HTML

Build accessible bar graphs with HTML/CSS, SVG, or Chart.js. Copy complete examples, choose the right approach, and troubleshoot common issues.

By the ScreenshotNeo team1 October 202610 min read

Short answer: HTML provides the structure for a bar graph; CSS, SVG, or JavaScript draws the bars. Use HTML and CSS for a small static chart, inline SVG for scalable and individually addressable graphics, or Chart.js when data, responsiveness, and interaction matter.

A useful bar graph has category labels, numeric values, a shared scale, and a clear baseline. The examples below use the same sales data so you can compare each technique.

1. Choose an implementation

Approach Best for Trade-offs
HTML and CSS A few static values and no chart dependency You must calculate bar sizes and build your own labels, scale, and responsive behavior.
Inline SVG Scalable graphics, print output, and individually addressable bars You must generate coordinates and manage interactions yourself.
Canvas with Chart.js Data-driven charts, responsive layouts, tooltips, and configuration Canvas is not semantic HTML, so you need an accessible name and a text or table alternative.

Keep the baseline consistent. A truncated axis can make small differences look large; use a zero baseline unless there is a clear reason to show a different range.

2. Create a static bar graph with HTML and CSS

This version keeps the data in normal HTML, which makes the values easy to read and index. Each bar is sized as a percentage of the maximum value.

<!doctype html>
<html lang="en">
<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <title>Monthly sales</title>
  <style>
    :root {
      --bar: #2563eb;
      --grid: #dbe3ef;
      --text: #172033;
    }

    body {
      font-family: system-ui, sans-serif;
      color: var(--text);
      margin: 2rem;
    }

    .chart {
      max-width: 46rem;
    }

    .chart h1 {
      font-size: 1.4rem;
    }

    .bars {
      display: grid;
      gap: .75rem;
      list-style: none;
      margin: 0;
      padding: 0;
    }

    .bar-row {
      display: grid;
      grid-template-columns: 7rem 1fr 4rem;
      align-items: center;
      gap: .75rem;
    }

    .track {
      background: repeating-linear-gradient(
        to right,
        var(--grid) 0,
        var(--grid) 1px,
        transparent 1px,
        transparent 20%
      );
      border-radius: .25rem;
      min-height: 1.5rem;
    }

    .bar {
      background: var(--bar);
      border-radius: .25rem;
      color: white;
      display: block;
      min-width: 2px;
      padding: .25rem .5rem;
      box-sizing: border-box;
      text-align: right;
    }

    .value {
      font-variant-numeric: tabular-nums;
      text-align: right;
    }

    @media (max-width: 34rem) {
      .bar-row { grid-template-columns: 5rem 1fr 3rem; gap: .4rem; }
    }
  </style>
</head>
<body>
  <main class="chart" aria-labelledby="sales-title">
    <h1 id="sales-title">Monthly sales</h1>
    <p>Units sold, January to June. The longest bar represents 240 units.</p>
    <ul class="bars">
      <li class="bar-row">
        <span>January</span>
        <span class="track"><span class="bar" style="width: 50%">120</span></span>
        <span class="value">120</span>
      </li>
      <li class="bar-row">
        <span>February</span>
        <span class="track"><span class="bar" style="width: 66.67%">160</span></span>
        <span class="value">160</span>
      </li>
      <li class="bar-row">
        <span>March</span>
        <span class="track"><span class="bar" style="width: 41.67%">100</span></span>
        <span class="value">100</span>
      </li>
      <li class="bar-row">
        <span>April</span>
        <span class="track"><span class="bar" style="width: 83.33%">200</span></span>
        <span class="value">200</span>
      </li>
      <li class="bar-row">
        <span>May</span>
        <span class="track"><span class="bar" style="width: 100%">240</span></span>
        <span class="value">240</span>
      </li>
      <li class="bar-row">
        <span>June</span>
        <span class="track"><span class="bar" style="width: 75%">180</span></span>
        <span class="value">180</span>
      </li>
    </ul>
  </main>
</body>
</html>

Calculating widths from data

For a value v and a maximum value max, set the width to (v / max) * 100 percent. Clamp or validate values before rendering so negative values, missing values, or a zero maximum do not produce invalid widths.

const values = [120, 160, 100, 200, 240, 180];
const max = Math.max(...values, 1);
const width = (value / max) * 100;
// Use width + '%' when creating each bar.

3. Draw a scalable bar graph with SVG

SVG is useful when bars need to scale cleanly, print sharply, or be selected individually. An embedded SVG can include a <title> and <desc> for a text description.

<svg viewBox="0 0 620 360" role="img" aria-labelledby="chart-title chart-desc">
  <title id="chart-title">Monthly sales</title>
  <desc id="chart-desc">May is highest at 240 units; March is lowest at 100 units.</desc>

  <line x1="70" y1="300" x2="590" y2="300" stroke="#334155" />
  <line x1="70" y1="40" x2="70" y2="300" stroke="#334155" />

  <!-- Scale: 260 pixels represents 240 units. -->
  <rect x="90"  y="170" width="55" height="130" fill="#2563eb">
    <title>January: 120 units</title>
  </rect>
  <rect x="175" y="127" width="55" height="173" fill="#2563eb">
    <title>February: 160 units</title>
  </rect>
  <rect x="260" y="192" width="55" height="108" fill="#2563eb">
    <title>March: 100 units</title>
  </rect>
  <rect x="345" y="83" width="55" height="217" fill="#2563eb">
    <title>April: 200 units</title>
  </rect>
  <rect x="430" y="40" width="55" height="260" fill="#2563eb">
    <title>May: 240 units</title>
  </rect>
  <rect x="515" y="105" width="55" height="195" fill="#2563eb">
    <title>June: 180 units</title>
  </rect>

  <text x="95" y="325">Jan</text>
  <text x="180" y="325">Feb</text>
  <text x="265" y="325">Mar</text>
  <text x="350" y="325">Apr</text>
  <text x="435" y="325">May</text>
  <text x="520" y="325">Jun</text>
</svg>

For generated SVG, map each value to a bar height, subtract that height from the baseline to obtain the y coordinate, and keep all bars on the same scale. Add visible axis labels when readers need exact values.

4. Build a data-driven chart with Chart.js

Chart.js documents a canvas-based bar chart with a chart-specific container, a type: 'bar' configuration, labels, datasets, and a y-axis baseline. The CDN example below follows that structure. For a production build, you can install Chart.js through your package manager and pin the dependency version used by your application.

<!doctype html>
<html lang="en">
<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <title>Chart.js bar graph</title>
  <script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
  <style>
    .chart-container { max-width: 720px; height: 380px; }
  </style>
</head>
<body>
  <main>
    <h1>Monthly sales</h1>
    <div class="chart-container">
      <canvas id="sales-chart"
        role="img"
        aria-label="Bar chart of monthly sales: January 120, February 160, March 100, April 200, May 240, June 180 units.">
        <p>Monthly sales: January 120, February 160, March 100, April 200, May 240, June 180 units.</p>
      </canvas>
    </div>
    <table>
      <caption>Monthly sales data</caption>
      <thead><tr><th>Month</th><th>Units</th></tr></thead>
      <tbody>
        <tr><td>January</td><td>120</td></tr>
        <tr><td>February</td><td>160</td></tr>
        <tr><td>March</td><td>100</td></tr>
        <tr><td>April</td><td>200</td></tr>
        <tr><td>May</td><td>240</td></tr>
        <tr><td>June</td><td>180</td></tr>
      </tbody>
    </table>
  </main>

  <script>
    const canvas = document.getElementById('sales-chart');
    new Chart(canvas, {
      type: 'bar',
      data: {
        labels: ['January', 'February', 'March', 'April', 'May', 'June'],
        datasets: [{
          label: 'Units sold',
          data: [120, 160, 100, 200, 240, 180],
          backgroundColor: '#2563eb'
        }]
      },
      options: {
        responsive: true,
        maintainAspectRatio: false,
        scales: {
          y: {
            beginAtZero: true,
            title: { display: true, text: 'Units' }
          },
          x: {
            title: { display: true, text: 'Month' }
          }
        }
      }
    });
  </script>
</body>
</html>

Chart.js supports horizontal bars by setting indexAxis: 'y'. Its bar configuration also includes controls for bar thickness and category spacing, which help when labels are long or many categories must fit.

options: {
  indexAxis: 'y',
  scales: { x: { beginAtZero: true } },
  elements: {
    bar: { borderWidth: 1 }
  }
}

5. Make the graph accessible

  • Give the chart a visible heading and describe the unit and time range.
  • Provide a data table or concise text summary for the exact category/value pairs.
  • For canvas, add an accessible name with role="img" and aria-label, plus fallback content. Canvas content is not exposed as semantic HTML; see MDN’s canvas reference and Chart.js accessibility guidance.
  • Use sufficient color contrast and do not make color the only way to distinguish series.
  • For SVG, include <title> and <desc>, label axes, and ensure the graphic has an appropriate accessible name.
  • Interactive charts need keyboard and device-independent operation. Static automated checks cannot confirm every interaction; the W3C Graphics-ARIA recommendation describes this limitation.

6. Handle responsive sizing and large datasets

Keep a responsive Chart.js canvas inside a sized container. Setting only CSS dimensions on a canvas can scale its bitmap and make it blurry; set intrinsic dimensions when drawing directly, or follow the chart library’s responsive configuration. For long category names, switch to a horizontal chart, wrap labels, or reduce the number of categories. For hundreds or thousands of points, aggregate data, paginate categories, or use a visualization designed for dense data instead of rendering every label at once.

7. Troubleshoot common problems

Symptom Cause Fix
Bars have the wrong relative length Each bar uses a different maximum or scale. Calculate one shared maximum and use the same baseline for every value.
Bars disappear A zero, negative, null, or non-numeric value creates an invalid size. Validate input, decide how negative values should be drawn, and handle an empty or zero-only dataset explicitly.
Chart.js says Chart is not defined The library script did not load before your chart code. Check the script URL and network policy, and run initialization after the library has loaded.
Canvas looks stretched or blurry CSS changed the display size without matching the intrinsic bitmap size. Use a correctly sized container and the library’s responsive settings, or set canvas width and height properties for custom drawing.
Long labels overlap There is not enough horizontal space. Use indexAxis: 'y', shorten labels, rotate or wrap them, or increase the chart container height.
Screen readers receive no values Canvas pixels do not expose the data as semantic HTML. Add an accessible name and a nearby data table or textual summary.
SVG bars are clipped The viewBox or bar coordinates exceed the drawing area. Increase the viewBox, reduce bar dimensions, and reserve space for labels and margins.

8. Performance, reliability, and cost

  • HTML/CSS and inline SVG have no chart-library download, which is useful for a small static graph.
  • Chart.js adds a dependency and script load, but reduces the amount of chart behavior you must implement yourself.
  • Do not redraw on every keystroke or resize event without throttling; update only when data changes.
  • Use stable scales and deterministic formatting so screenshots, exports, and visual comparisons do not change unexpectedly.
  • For server-rendered pages, include a text table so the values remain available if JavaScript fails.

9. Capture the finished graph as an image or PDF

Once the graph is in a web page, you can capture it for documentation, reports, or previews. ScreenshotNeo is a website screenshot API and MCP server: it can capture PNG, JPEG, WebP, or PDF output from one GET request. It removes cookie banners, newsletter popups, and chat widgets before capture; bot checks, blank pages, timeouts, failed loads, and cache hits are not billed. See the ScreenshotNeo documentation for all options.

Or skip the browser setup

Use ScreenshotNeo when you want the rendered HTML graph without configuring a headless browser. Cookie banners, popups, and chat widgets are removed before the shot; bot checks, blank pages, and failed loads are never billed. Its MCP server lets AI agents take screenshots, and 1,000 screenshots per month are free with no card; paid plans start at $5 for 3,000 shots.

curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://example.com/chart.html -o chart.webp
import requests

r = requests.get(
    "https://api.screenshotneo.com/v1/shot",
    params={"access_key": "YOUR_API_KEY", "url": "https://example.com/chart.html"},
    timeout=90,
)
r.raise_for_status()
open("chart.webp", "wb").write(r.content)
const q = new URLSearchParams({
  access_key: 'YOUR_API_KEY',
  url: 'https://example.com/chart.html'
});
const res = await fetch(`https://api.screenshotneo.com/v1/shot?${q}`);
if (!res.ok) throw new Error(`Screenshot failed: ${res.status}`);
const image = Buffer.from(await res.arrayBuffer());
await import('node:fs/promises').then(fs => fs.writeFile('chart.webp', image));

ScreenshotNeo also supports full-page capture, element selectors, custom CSS and JavaScript, waits, resource blocking, device presets, retina scale, PDF settings, caching, signed links, asynchronous jobs, bulk capture, and usage reporting. The response includes X-Page-Verdict and X-Billed headers so your application can see whether a clean page was captured and billed. Create a free ScreenshotNeo account to get 1,000 screenshots each month with no card.

10. FAQ

Can I create a bar graph with only HTML?

HTML alone supplies structure, not proportional drawing. Pair it with CSS, or embed SVG markup directly in the document.

Should I use SVG or Canvas?

Choose SVG when you need scalable, individually addressable shapes and built-in graphic descriptions. Choose canvas or Chart.js for data-driven rendering and chart configuration, then add text alternatives for accessibility.

How do I make a horizontal bar chart?

With Chart.js, set indexAxis: 'y'. With HTML/CSS or SVG, swap the category and value dimensions and keep one shared horizontal scale.

Why should the y-axis start at zero?

A zero baseline makes bar lengths directly comparable. Use another baseline only when you clearly label it and the visual goal requires a narrower range.