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:
- Zero JavaScript by default (HTML/CSS output)
- Server-side rendering (fast initial page loads)
- Progressive enhancement (add JS only when needed)
- Performance (minimal bundle sizes)
Table of ContentsLink to section
- Installation
- Basic Setup
- Chart Components
- Dynamic Data with Content Collections
- Theming
- Performance Optimization
- Examples
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
- Always include accessible data tables - Screen readers need them
- Use semantic HTML -
<figure>,<figcaption>,role="img" - Keep data in frontmatter - Separate content from presentation
- Create reusable components - DRY principle for chart patterns
- Test without JavaScript - Verify charts work with JS disabled
- 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