Skip to main content
Casca
Theme

Astro Integration GuideLink to section

This guide shows how to use Casca with Astro, a modern static site generator optimized for performance and content-focused websites.

Casca is an ideal match for Astro because both prioritize:


Table of ContentsLink to section


InstallationLink to section

1. Install CascaLink to section

npm install @skellvin/casca
# or
bun add @skellvin/casca
# or
pnpm add casca

2. Import CSS in Your LayoutLink to section

Option A: Global import (recommended for most sites)

---
// src/layouts/Layout.astro
import '@skellvin/casca/core'; // Zero globals, bring your own theme
// or
import '@skellvin/casca'; // Four-mood all-in-one, Solar by default
---

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>{Astro.props.title}</title>
  </head>
  <body>
    <slot />
  </body>
</html>

Option B: Page-level import (for specific pages only)

---
// src/pages/dashboard.astro
import Layout from '../layouts/Layout.astro';
import '@skellvin/casca/core';
---

<Layout title="Dashboard">
  <div class="casca casca-figure">
    <!-- Chart markup -->
  </div>
</Layout>

Basic SetupLink to section

Simple Bar Chart ExampleLink to section

---
// src/pages/sales.astro
import Layout from '../layouts/Layout.astro';
import '@skellvin/casca/core';

const salesData = [
  { month: 'Jan', value: 65 },
  { month: 'Feb', value: 80 },
  { month: 'Mar', value: 45 },
  { month: 'Apr', value: 90 },
  { month: 'May', value: 72 },
  { month: 'Jun', value: 55 }
];
---

<Layout title="Monthly Sales">
  <figure class="casca casca-figure" role="img" aria-labelledby="sales-title">
    <figcaption>
      <div id="sales-title" class="casca-title">Monthly Sales</div>
      <div class="casca-subtitle">Revenue in thousands</div>
    </figcaption>

    <!-- Accessible data table -->
    <table class="casca-data">
      <caption>Monthly Sales Data</caption>
      <thead>
        <tr><th>Month</th><th>Revenue</th></tr>
      </thead>
      <tbody>
        {salesData.map(({month, value}) => (
          <tr>
            <th scope="row">{month}</th>
            <td>${value},000</td>
          </tr>
        ))}
      </tbody>
    </table>

    <!-- Visual chart -->
    <div class="casca-bar" aria-hidden="true">
      <div class="casca-bar-group">
        {salesData.map(({value}) => (
          <div class="casca-bar-item">
            <div
              class="casca-bar-value"
              style={`--value: ${value}%; --color: var(--casca-color-1)`}
              data-label={`$${value}k`}
            />
          </div>
        ))}
      </div>
      <div class="casca-bar-labels">
        {salesData.map(({month}) => (
          <span class="casca-bar-label">{month}</span>
        ))}
      </div>
    </div>
  </figure>
</Layout>

Chart ComponentsLink to section

Create reusable Astro components for common chart patterns:

Bar Chart ComponentLink to section

---
// src/components/BarChart.astro
interface Props {
  title: string;
  subtitle?: string;
  data: Array<{ label: string; value: number }>;
  color?: string;
}

const { title, subtitle, data, color = 'var(--casca-color-1)' } = Astro.props;
const chartId = `chart-${Math.random().toString(36).slice(2, 9)}`;
---

<figure class="casca casca-figure" role="img" aria-labelledby={`${chartId}-title`}>
  <figcaption>
    <div id={`${chartId}-title`} class="casca-title">{title}</div>
    {subtitle && <div class="casca-subtitle">{subtitle}</div>}
  </figcaption>

  <table class="casca-data">
    <caption>{title} Data</caption>
    <thead>
      <tr><th>Category</th><th>Value</th></tr>
    </thead>
    <tbody>
      {data.map(({label, value}) => (
        <tr>
          <th scope="row">{label}</th>
          <td>{value}</td>
        </tr>
      ))}
    </tbody>
  </table>

  <div class="casca-bar" aria-hidden="true">
    <div class="casca-bar-group">
      {data.map(({value}) => (
        <div class="casca-bar-item">
          <div
            class="casca-bar-value"
            style={`--value: ${value}%; --color: ${color}`}
          />
        </div>
      ))}
    </div>
    <div class="casca-bar-labels">
      {data.map(({label}) => (
        <span class="casca-bar-label">{label}</span>
      ))}
    </div>
  </div>
</figure>

Usage:

---
import BarChart from '../components/BarChart.astro';

const data = [
  { label: 'Q1', value: 65 },
  { label: 'Q2', value: 80 },
  { label: 'Q3', value: 45 },
  { label: 'Q4', value: 90 }
];
---

<BarChart
  title="Quarterly Revenue"
  subtitle="2024 Performance"
  data={data}
/>

Pie Chart ComponentLink to section

---
// src/components/PieChart.astro
interface Props {
  title: string;
  subtitle?: string;
  data: Array<{ label: string; value: number; color: string }>;
}

const { title, subtitle, data } = Astro.props;

// Calculate cumulative angles for conic gradient
const total = data.reduce((sum, {value}) => sum + value, 0);
let cumulativeAngle = 0;
const angles = data.map(({value}, i) => {
  if (i === data.length - 1) return null; // Last segment doesn't need angle
  cumulativeAngle += (value / total) * 360;
  return cumulativeAngle;
});

const chartId = `pie-${Math.random().toString(36).slice(2, 9)}`;
---

<figure class="casca casca-figure" role="img" aria-labelledby={`${chartId}-title`}>
  <figcaption>
    <div id={`${chartId}-title`} class="casca-title">{title}</div>
    {subtitle && <div class="casca-subtitle">{subtitle}</div>}
  </figcaption>

  <table class="casca-data">
    <caption>{title} Data</caption>
    <thead>
      <tr><th>Category</th><th>Value</th></tr>
    </thead>
    <tbody>
      {data.map(({label, value}) => (
        <tr>
          <th scope="row">{label}</th>
          <td>{value}</td>
        </tr>
      ))}
    </tbody>
  </table>

  <div class="casca-pie" aria-hidden="true">
    <div
      class="casca-pie-chart"
      data-segments={data.length}
      style={`
        ${angles.map((angle, i) =>
          angle ? `--angle-${i + 1}: ${angle}deg;` : ''
        ).join(' ')}
        ${data.map(({color}, i) =>
          `--color-${i + 1}: ${color};`
        ).join(' ')}
      `}
    />
  </div>

  <div class="casca-legend">
    {data.map(({label, value, color}) => (
      <div class="casca-legend-item">
        <span class="casca-legend-swatch" style={`--_color: ${color}`} />
        {label} ({Math.round((value / total) * 100)}%)
      </div>
    ))}
  </div>
</figure>

Dynamic Data with Content CollectionsLink to section

Use Astro's Content Collections to fetch data from markdown/JSON files:

1. Define Collection SchemaLink to section

// src/content/config.ts
import { defineCollection, z } from 'astro:content';

const metricsCollection = defineCollection({
  type: 'data',
  schema: z.object({
    title: z.string(),
    subtitle: z.string().optional(),
    chartType: z.enum(['bar', 'pie', 'line']),
    data: z.array(z.object({
      label: z.string(),
      value: z.number(),
      color: z.string().optional()
    }))
  })
});

export const collections = {
  'metrics': metricsCollection
};

2. Create Data FilesLink to section

# src/content/metrics/q4-sales.json
{
  "title": "Q4 Sales Performance",
  "subtitle": "October - December 2024",
  "chartType": "bar",
  "data": [
    { "label": "Oct", "value": 85 },
    { "label": "Nov", "value": 92 },
    { "label": "Dec", "value": 78 }
  ]
}

3. Render Charts from CollectionsLink to section

---
// src/pages/metrics/[slug].astro
import { getCollection } from 'astro:content';
import Layout from '../../layouts/Layout.astro';
import BarChart from '../../components/BarChart.astro';
import '@skellvin/casca/core';

export async function getStaticPaths() {
  const metrics = await getCollection('metrics');
  return metrics.map(metric => ({
    params: { slug: metric.id },
    props: { metric }
  }));
}

const { metric } = Astro.props;
const { title, subtitle, chartType, data } = metric.data;
---

<Layout title={title}>
  {chartType === 'bar' && (
    <BarChart title={title} subtitle={subtitle} data={data} />
  )}
  {/* Add other chart types as needed */}
</Layout>

ThemingLink to section

Using casca-core with Custom ThemeLink to section

If you're using @skellvin/casca/core, create a theme bridge:

/* src/styles/casca-theme.css */
.casca {
  /* Map to your existing design tokens */
  --casca-color-1: var(--color-primary, #228be6);
  --casca-color-2: var(--color-secondary, #12b886);
  --casca-color-3: var(--color-accent, #fd7e14);

  --casca-gray-0: var(--bg-primary, #ffffff);
  --casca-gray-8: var(--text-primary, #343a40);

  /* Override chart dimensions */
  --casca-height: 400px;
  --casca-bar-radius: 0.5rem;
}

@media (prefers-color-scheme: dark) {
  .casca {
    --casca-gray-0: #1a1a1a;
    --casca-gray-8: #e0e0e0;
  }
}

Import in layout:

---
// src/layouts/Layout.astro
import '@skellvin/casca/core';
import '../styles/casca-theme.css';
---

Using casca.css with OverridesLink to section

If using the full bundle, override specific tokens after importing @skellvin/casca. Pages without .casca-theme-picker[data-casca-theme-picker] markup or body[data-casca-theme] render with Solar as the unscoped base; when picker DOM is present, checked mood overlays can remap the same public tokens.

---
import '@skellvin/casca';
---

<style is:global>
  :root {
    --casca-color-1: #0066cc; /* Your brand color */
  }
</style>

Performance OptimizationLink to section

1. View Transitions (Astro 3.0+)Link to section

Casca works seamlessly with Astro's View Transitions:

---
// src/layouts/Layout.astro
import { ViewTransitions } from 'astro:transitions';
import '@skellvin/casca/core';
---

<html>
  <head>
    <ViewTransitions />
  </head>
  <body>
    <slot />
  </body>
</html>

Charts will persist across navigation without re-rendering.

2. Partial HydrationLink to section

Since Casca is pure CSS, you get zero JavaScript overhead. If you add interactive features later, use Astro's partial hydration:

---
import InteractiveChart from '../components/InteractiveChart.tsx';
---

<!-- Only hydrate this component when visible -->
<InteractiveChart client:visible>
  <div class="casca casca-figure">
    <!-- Static chart content -->
  </div>
</InteractiveChart>

3. CSS Inlining (Critical CSS)Link to section

For maximum performance, inline critical Casca CSS:

---
// Extract only the CSS needed for above-the-fold charts
---

<style is:inline>
  /* Inline critical Casca styles here */
  .casca { /* ... */ }
  .casca-bar { /* ... */ }
</style>

<link rel="stylesheet" href="/casca.css" media="print" onload="this.media='all'">

ExamplesLink to section

Dashboard PageLink to section

---
// src/pages/dashboard.astro
import Layout from '../layouts/Layout.astro';
import BarChart from '../components/BarChart.astro';
import PieChart from '../components/PieChart.astro';
import '@skellvin/casca/core';

const salesData = [
  { label: 'Jan', value: 65 },
  { label: 'Feb', value: 80 },
  { label: 'Mar', value: 45 }
];

const marketShareData = [
  { label: 'Product A', value: 35, color: 'var(--casca-color-1)' },
  { label: 'Product B', value: 25, color: 'var(--casca-color-2)' },
  { label: 'Product C', value: 40, color: 'var(--casca-color-3)' }
];
---

<Layout title="Dashboard">
  <main>
    <h1>Analytics Dashboard</h1>

    <div class="grid">
      <BarChart
        title="Monthly Sales"
        subtitle="Q1 2024"
        data={salesData}
      />

      <PieChart
        title="Market Share"
        subtitle="By Product"
        data={marketShareData}
      />
    </div>
  </main>
</Layout>

<style>
  .grid {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(400px, 1fr));
    gap: 2rem;
    margin-top: 2rem;
  }
</style>

MDX IntegrationLink to section

Use charts in MDX content:

---
// src/content/posts/q4-report.mdx
title: Q4 Performance Report
---

import BarChart from '../../components/BarChart.astro';

# Q4 Performance Report

Our sales exceeded expectations this quarter:

<BarChart
  title="Q4 Sales"
  data={[
    { label: 'Oct', value: 85 },
    { label: 'Nov', value: 92 },
    { label: 'Dec', value: 78 }
  ]}
/>

We saw a **15% increase** in November.

Best PracticesLink to section

  1. Always include accessible data tables - Screen readers need them
  2. Use semantic HTML - <figure>, <figcaption>, role="img"
  3. Keep data in frontmatter - Separate content from presentation
  4. Create reusable components - DRY principle for chart patterns
  5. Test without JavaScript - Verify charts work with JS disabled
  6. Use Content Collections - Type-safe data management

TroubleshootingLink to section

Charts not renderingLink to section

Issue: CSS not loaded Solution: Ensure import '@skellvin/casca/core' is in your layout or page

Styles conflicting with Astro defaultsLink to section

Issue: Global CSS reset affecting charts Solution: Use @skellvin/casca/core and scope your reset to exclude .casca

/* Your global reset */
* {
  margin: 0;
  padding: 0;
}

/* Don't reset Casca */
.casca,
.casca * {
  all: revert;
}

Dark mode not workingLink to section

Issue: Astro theme toggle conflicts Solution: Use Casca's data-theme attribute:

<div class="casca casca-figure" data-theme={isDark ? 'dark' : 'light'}>
  <!-- chart -->
</div>

Further ReadingLink to section


Example RepositoryLink to section

See a full working example: (coming soon)

git clone <your-host>/yourorg/casca-astro-example
cd casca-astro-example
npm install
npm run dev