Adding Interactive Charts in Astro

So here’s the thing—I was knee-deep in redesigning my website last month, trying to make it more blog-focused, when I realized something was missing. Sure, I’d added a search bar and cleaned up the navigation, but my data visualizations? They were, honestly, pretty boring.

That’s when I stumbled across ApexCharts, and wow, what a game-changer. If you’ve been wrestling with static charts or wondering how to make your Astro site more interactive, you’re gonna love this.

Let’s Start with Something Cool

Before we get into the weeds, check this out—here’s what we’re building toward:

Pretty slick, right? This mixed chart combines columns, areas, and lines all in one visualization. Trust me, your users will be impressed.

Getting Your Hands Dirty

Alright, let’s talk setup. First things first—you’ll need to grab ApexCharts:

# I prefer npm, but use whatever floats your boat
npm install apexcharts @types/apexcharts

# Or if you're team Yarn
yarn add apexcharts @types/apexcharts

Now here’s where most tutorials get it wrong. They’ll show you one chart and call it a day. But what happens when you want multiple charts on the same page? Chaos, that’s what.

🚨 Learned This the Hard Way

Each ApexChart needs its own unique container ID. I spent two hours debugging why my second chart wasn't showing up before I realized they were both trying to use '#chart'. Don't make my mistake.

Building the Chart Component That Actually Works

Here’s the component I’ve been using—it handles multiple charts, accessibility, and won’t break when you throw real data at it:

---
// ChartsComponent.astro
interface Props {
  chart: string;
  chartId: string;
  title?: string;
  description?: string;
}

const { chart, chartId, title, description } = Astro.props;
---

<div class="chart-container w-full h-[400px] p-4 bg-white rounded-lg shadow-md">
  {title && <h2 id={`${chartId}-title`} class="text-lg font-semibold mb-2">{title}</h2>}
  {description && <p id={`${chartId}-desc`} class="text-sm text-gray-600 mb-4">{description}</p>}
  <div 
    id={chartId}
    role="img"
    aria-labelledby={title ? `${chartId}-title` : undefined}
    aria-describedby={description ? `${chartId}-desc` : undefined}
  ></div>
</div>

<script src="https://cdn.jsdelivr.net/npm/apexcharts"></script>

<script is:inline define:vars={{ chartData: chart, chartId }}>
try {
  const options = JSON.parse(chartData);
  
  // Add some nice default animations and accessibility
  options.chart = {
    ...options.chart,
    animations: {
      enabled: true,
      easing: 'easeinout',
      speed: 800,
      animateGradually: {
        enabled: true,
        delay: 150
      },
      dynamicAnimation: {
        enabled: true,
        speed: 350
      }
    },
    accessibility: {
      enabled: true,
      announceNewData: {
        enabled: true,
        announcementPolicy: 'all'
      }
    }
  };

  document.addEventListener('DOMContentLoaded', function() {
    const chart = new ApexCharts(document.querySelector(`#${chartId}`), options);
    chart.render().catch(console.error);
  });
} catch (error) {
  console.error('Error rendering chart:', error);
  document.querySelector(`#${chartId}`)?.insertAdjacentHTML(
    'beforeend',
    '<div class="text-red-500">Error rendering chart. Please check console for details.</div>'
  );
}
</script>

<style>
.chart-container {
  min-height: 400px;
  /* Making sure it looks good and accessible */
  --chart-background: theme('colors.white');
  --chart-text: theme('colors.gray.900');
}

@media (prefers-reduced-motion: reduce) {
  .chart-container {
    --chart-animation-duration: 0s;
  }
}
</style>

What Makes This Component Different

Let me break down what’s happening here, because there’s more going on than meets the eye:

Unique IDs for everything. Each chart gets its own container, which means you can have as many as you want without them stepping on each other’s toes.

Built-in error handling. Because let’s face it, JSON parsing can go wrong, and when it does, you want to know about it instead of staring at a blank div wondering what happened.

Accessibility baked in. Screen readers can actually understand what your charts are showing, which is something most people forget about.

Responsive by default. The charts adapt to their container, so they’ll look good on mobile without extra work.

Putting It All Together

Want to see multiple charts in action? Here’s how you’d use this component:

<ChartsComponent 
  chartId="line-chart"
  title="Sales Growth"
  description="Monthly sales data for the past year"
  chart={`
    {
      "chart": {
        "type": "line",
        "height": 350
      },
      "series": [{
        "name": "Sales",
        "data": [30, 40, 35, 50, 49, 60, 70, 91, 125]
      }],
      "xaxis": {
        "categories": [1991, 1992, 1993, 1994, 1995, 1996, 1997, 1998, 1999]
      },
      "colors": ["#1f77b4"]
    }
  `} 
/>

And here’s what that looks like:

Sales Growth

Monthly sales data for the past year

Clean, simple, and it actually works.

📊 JSON Gotcha

Make sure your chart configuration is properly escaped JSON. I've seen people try to pass JavaScript objects directly and wonder why nothing renders. JSON.stringify() is your friend here.

The Real Talk Section

Look, I’m not gonna sugarcoat this—getting charts to play nice with Astro’s build process can be a bit finicky at first. You might run into hydration issues, or your charts might render fine in development but break in production.

Here’s what I’ve learned from building this stuff:

Always test your chart configs in isolation first. Build a simple HTML file with just ApexCharts and your data. Get it working there, then bring it into Astro.

Watch out for SSR issues. Since charts need the DOM to exist, make sure you’re waiting for DOMContentLoaded before trying to render anything.

Keep your data simple initially. Start with basic arrays and objects. You can always make it more complex once you’ve got the foundation solid.

Performance Tip

If you're loading charts above the fold, consider lazy loading ApexCharts only when needed. Those extra 100KB can hurt your page speed scores.

Where to Go From Here

Once you’ve got the basics down, ApexCharts opens up a whole world of possibilities. You can build real-time dashboards, interactive data explorers, or just make your blog posts way more engaging with some slick visualizations.

The component I’ve shown you here is just the starting point. You could extend it to handle themes, add export functionality, or even connect it to your CMS for dynamic chart generation.

But honestly? Start simple. Get one chart working perfectly, then expand from there. There’s nothing worse than over-engineering something that should be straightforward.


And that’s it! You’ve now got everything you need to add professional-looking charts to your Astro site. The best part? Your visitors will think you’re some kind of data visualization wizard, when really you just followed a tutorial from some random developer’s blog.

Now go forth and chart all the things.

Stay up to date

Get notified when I publish something new, and unsubscribe at any time.

Join 44 other subscribers.

Contact Pavlin Gunov at contact@pavlinbg.com

Phone: +1234567890

Address: Sofia, Bulgaria

© 2025 Pavlin

Instagram GitHub