Skip to content

Template Site: Astro Components Conversion Roadmap

Section titled “Template Site: Astro Components Conversion Roadmap”

Approved: 2025-11-06
Vision: Transform from Svelte-heavy architecture to Astro-first with AlpineJS for interactivity
Target Completion: 2-3 weeks
Build Status: Already stable (0 errors) after Lesson 10 conversions


  • Header.astro - Converted, working with vanilla JS
  • Footer.astro - Converted, working
  • ThemeSwitcher.astro - Converted, working with vanilla JS
  • Build succeeds - pnpm build completes without errors
  • ⚠️ Limited functionality - Pages exist but limited content/features
  • ⚠️ Mobile menu - Works but could be enhanced with Alpine
  • ⚠️ Theme switching - Functional but basic implementation

90% Astro Components + 10% AlpineJS for simple, maintainable, FOSS-aligned web development


Phase 1: Foundation & Infrastructure (Week 1)

Section titled “Phase 1: Foundation & Infrastructure (Week 1)”

Effort: 1-2 hours
Owner: AI Assistant

Tasks:

  • Install AlpineJS via package manager or CDN
  • Verify it loads in dev and build
  • Create Alpine utility components directory: src/components/alpine/
  • Document Alpine patterns used in this project

Checklist:

- [ ] pnpm add alpinejs (or verify existing)
- [ ] Add to BaseLayout.astro scripts if not present
- [ ] Test basic Alpine directive (x-data, @click) in Header
- [ ] Build succeeds with Alpine code

Resources:


1.2 Create Component Library Documentation

Section titled “1.2 Create Component Library Documentation”

Effort: 2-3 hours
Owner: AI Assistant + User Review

Create: docs/COMPONENT_LIBRARY.md

Contents:

  • Component naming conventions
  • When to use Astro vs Alpine vs Svelte
  • File structure and organization
  • Code examples for each tier
  • TypeScript/props patterns for Astro components

Example section structure:

## Component Decision Tree
Does component need to be interactive?
├─ NO → Use Astro component
├─ YES → Change after page load?
├─ NO (only on user interaction) → AlpineJS
└─ YES (real-time updates) → Consider Svelte/React

1.3 Create Alpine Component Wrapper Pattern

Section titled “1.3 Create Alpine Component Wrapper Pattern”

Effort: 1-2 hours
Owner: AI Assistant

Create template: src/components/alpine/AlpineComponentBase.astro

---
/**
* Reusable pattern for Alpine-powered Astro components
*
* Usage:
* <AlpineDropdown items={items} />
*/
interface Props {
id?: string;
class?: string;
}
const { id = '', class: className = '' } = Astro.props;
---
<div
id={id}
class={className}
x-data="componentName()"
x-init="init()"
>
<slot />
</div>
<script define:vars={{ /* any vars from frontmatter */ }}>
window.componentName = function() {
return {
// Alpine state here
init() {
// Initialize
}
}
}
</script>
<style scoped>
/* Component styles */
</style>

Phase 2: Convert Existing Svelte Components (Week 1-2)

Section titled “Phase 2: Convert Existing Svelte Components (Week 1-2)”

Effort: 1 hour
Owner: AI Assistant

List all .svelte files currently in use:

Terminal window
find src/components -name "*.svelte" -type f

Expected list:

  • List generated and categorized by purpose

Categorize by:

  1. Tier 1 - Pure Astro (no interactivity needed)
  2. Tier 2 - Alpine candidates (simple interactivity)
  3. Tier 3 - Keep as Svelte (genuinely complex)

2.2 Convert Tier 1 Components (Pure Astro)

Section titled “2.2 Convert Tier 1 Components (Pure Astro)”

Effort: 3-4 hours
Owner: AI Assistant

Target Components:

  • Button.svelte → Button.astro
  • Card.svelte → Card.astro
  • Hero.svelte → Hero.astro
  • Feature.svelte → Feature.astro
  • Price.svelte → Price.astro
  • Stats.svelte → Stats.astro
  • Grid.svelte → Grid.astro
  • Container.svelte → Container.astro
  • Section.svelte → Section.astro
  • Badge.svelte → Badge.astro
  • Alert.svelte → Alert.astro

Pattern for conversion:

Button.svelte
<script lang="ts">
interface Props {
variant?: 'primary' | 'secondary';
size?: 'sm' | 'md' | 'lg';
}
const { variant = 'primary', size = 'md' } = $props();
</script>
<button class={`btn btn-${variant} btn-${size}`}>
<slot />
</button>
<style>
.btn { /* styles */ }
</style>
Button.astro
---
interface Props {
variant?: 'primary' | 'secondary';
size?: 'sm' | 'md' | 'lg';
class?: string;
}
const { variant = 'primary', size = 'md', class: className = '' } = Astro.props;
---
<button class={`btn btn-${variant} btn-${size} ${className}`} {...Astro.props}>
<slot />
</button>
<style>
.btn { /* styles */ }
</style>

For each component:

  1. Create .astro version
  2. Copy props interface and styling
  3. Remove Svelte-specific code (script, runes, etc.)
  4. Test in dev with pnpm dev
  5. Verify build passes with pnpm build
  6. Delete original .svelte file

2.3 Convert Tier 2 Components (Alpine Enhancement)

Section titled “2.3 Convert Tier 2 Components (Alpine Enhancement)”

Effort: 4-5 hours
Owner: AI Assistant

Target Components:

  • Dropdown.svelte → Dropdown.astro (with Alpine)
  • Modal.svelte → Modal.astro (with Alpine)
  • Tabs.svelte → Tabs.astro (with Alpine)
  • Accordion.svelte → Accordion.astro (with Alpine)
  • Navbar toggle → Use Alpine for mobile menu
  • Form validation → Alpine directives

Pattern for Alpine conversion:

Dropdown.astro
---
interface Props {
trigger: string;
items: string[];
class?: string;
}
const { trigger, items, class: className = '' } = Astro.props;
---
<div class={`dropdown ${className}`} x-data="dropdown()" @click.away="open = false">
<button @click="open = !open" class="dropdown-trigger">
{trigger}
</button>
<ul x-show="open" class="dropdown-menu">
{items.map(item => <li><a href="#">{item}</a></li>)}
</ul>
</div>
<script>
window.dropdown = function() {
return {
open: false
}
}
</script>
<style>
.dropdown { position: relative; }
.dropdown-menu { position: absolute; top: 100%; left: 0; }
[x-show] { display: none; }
[x-show][style*="display"] { display: block; }
</style>

For each component:

  1. Create .astro version with Alpine
  2. Copy HTML structure
  3. Replace Svelte reactivity with Alpine directives
  4. Add Alpine state object (x-data)
  5. Test interactivity in dev
  6. Verify build passes
  7. Delete original .svelte file

2.4 Decide on Tier 3 Components (Keep or Assess)

Section titled “2.4 Decide on Tier 3 Components (Keep or Assess)”

Effort: 1-2 hours
Owner: User Review + AI Assessment

Decision for each:

  • If functionality can be achieved with Alpine → Convert to Astro + Alpine
  • If functionality truly requires complex state → Keep as Svelte (but isolate)
  • If functionality not used → Delete

Criteria:

  • Does it need real-time reactivity?
  • Does it have complex state dependencies?
  • Is ecosystem support critical?

Expected outcome:

  • Minimal or no Svelte components remain
  • All build friction eliminated

Effort: 2-3 hours
Owner: AI Assistant

Pages to create:

  • /docs - Overview.md content (text container: 25rem-80rem)
  • /components - Component showcase with examples
  • /blocks - Block showcase with categories
  • /pages - Page templates showcase

Each page should:

  • Use Astro components exclusively
  • Implement proper text container widths (25rem-80rem min/max)
  • Include example usage and code snippets
  • Support theme switching (via ThemeSwitcher Alpine component)

3.2 Implement Enhanced Theme Switcher with AlpineJS

Section titled “3.2 Implement Enhanced Theme Switcher with AlpineJS”

Effort: 2-3 hours
Owner: AI Assistant

Current: Basic theme cycling (Light/Dark/System)
Target: Dropdown with brand colors (Blue, Hunter Green, Gold)

Features:

  • Dropdown menu with color options
  • Visual indicator of current theme
  • Keyboard navigation (Arrow keys, Enter)
  • Persists to localStorage
  • Smooth transitions
  • Accessible (ARIA labels, focus states)

Implementation:

---
// components/ThemeSwitcher.astro - Enhanced version
---
<div class="theme-switcher" x-data="themeSwitcher()" @click.away="dropdownOpen = false">
<button
@click="dropdownOpen = !dropdownOpen"
class="theme-button"
:aria-label="`Current theme: ${currentTheme}`"
>
<span x-text="themeLabel"></span>
<span class="dropdown-arrow"></span>
</button>
<div x-show="dropdownOpen" class="theme-dropdown">
<button
@click="setTheme('light')"
:class="{ active: currentTheme === 'light' }"
>
☀️ Light
</button>
<button
@click="setTheme('dark')"
:class="{ active: currentTheme === 'dark' }"
>
🌙 Dark
</button>
<button
@click="setTheme('system')"
:class="{ active: currentTheme === 'system' }"
>
🔄 System
</button>
<hr class="theme-divider" />
<button
@click="setColor('blue')"
:class="{ active: currentColor === 'blue' }"
>
🔵 Blue (Primary)
</button>
<button
@click="setColor('green')"
:class="{ active: currentColor === 'green' }"
>
💚 Hunter Green
</button>
<button
@click="setColor('gold')"
:class="{ active: currentColor === 'gold' }"
>
🟡 Gold
</button>
</div>
</div>
<script define:vars={{ colors: { blue: '#3B82F6', green: '#355E3B', gold: '#E6BA1D' } }}>
window.themeSwitcher = function() {
return {
dropdownOpen: false,
currentTheme: 'system',
currentColor: 'blue',
themeLabel: 'System',
init() {
this.currentTheme = localStorage.getItem('theme-preference') || 'system';
this.currentColor = localStorage.getItem('color-preference') || 'blue';
this.updateLabel();
},
setTheme(theme) {
this.currentTheme = theme;
localStorage.setItem('theme-preference', theme);
this.applyTheme();
this.updateLabel();
},
setColor(color) {
this.currentColor = color;
localStorage.setItem('color-preference', color);
this.applyColor();
},
updateLabel() {
const labels = { light: '☀️ Light', dark: '🌙 Dark', system: '🔄 System' };
this.themeLabel = labels[this.currentTheme] || 'System';
},
applyTheme() {
const actual = this.currentTheme === 'system'
? (window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light')
: this.currentTheme;
document.documentElement.setAttribute('data-theme', actual);
},
applyColor() {
document.documentElement.setAttribute('data-color', this.currentColor);
}
}
}
</script>
<style>
.theme-switcher {
position: relative;
display: inline-block;
}
.theme-button {
display: inline-flex;
align-items: center;
gap: 0.5rem;
padding: 0.5rem 1rem;
background: hsl(var(--background));
border: 1px solid hsl(var(--border));
border-radius: var(--radius);
color: hsl(var(--foreground));
cursor: pointer;
transition: all 0.2s;
}
.theme-button:hover {
background: hsl(var(--accent));
}
.dropdown-arrow {
font-size: 0.75em;
transition: transform 0.2s;
}
.theme-dropdown {
position: absolute;
top: 100%;
left: 0;
margin-top: 0.5rem;
background: hsl(var(--background));
border: 1px solid hsl(var(--border));
border-radius: var(--radius);
box-shadow: 0 2px 8px rgba(0,0,0,0.1);
z-index: 1000;
min-width: 150px;
}
.theme-dropdown button {
display: block;
width: 100%;
padding: 0.75rem 1rem;
text-align: left;
background: none;
border: none;
cursor: pointer;
transition: background 0.2s;
}
.theme-dropdown button:hover {
background: hsl(var(--accent));
}
.theme-dropdown button.active {
background: hsl(var(--accent));
font-weight: 500;
}
.theme-divider {
margin: 0.5rem 0;
border: none;
border-top: 1px solid hsl(var(--border));
}
</style>

Effort: 2-3 hours
Owner: AI Assistant

Current: Vanilla JS works but basic
Target: Smooth Alpine-powered experience with keyboard support

Features:

  • Smooth open/close animation with Alpine
  • Keyboard navigation (Escape to close)
  • Focus trap in open menu
  • Chevron animation
  • Submenu handling
  • Touch/click outside to close

Effort: 3-4 hours
Owner: User + AI

Pages to create:

  • /blocks/hero - Hero variants with hr separators
  • /blocks/feature - Feature block variants
  • /blocks/cta - Call-to-action variants
  • /blocks/stats - Statistics block variants
  • /blocks/testimonials - Testimonial variants
  • /blocks/utility - Utility/misc blocks

Each page:

  • Display 3-5 variants of each block
  • Clear visual separation (use <hr> as per requirements)
  • Live examples with copy-able code
  • Responsive preview

Effort: 3-4 hours
Owner: User + AI

Pages to create:

  • /pages/homepage - Homepage template
  • /pages/about - About page template
  • /pages/contact - Contact page template
  • /pages/services - Services page template
  • /pages/error - 404 error page template

Each page:

  • Complete template with realistic content
  • All required components
  • Responsive design
  • Accessibility features

Effort: 1-2 hours
Owner: AI Assistant

Apply to all pages:

  • Min width: 25rem (400px)
  • Max width: 80rem (1280px)
  • Already defined in design-tokens.css
  • Use CSS variable: var(--container-text)

Implementation pattern:

<div class="container" style="max-width: var(--container-text)">
<!-- content -->
</div>

Verification:

  • Test at 320px viewport
  • Test at 1600px viewport
  • Content remains readable
  • Line length appropriate

Effort: 2-3 hours
Owner: User Review

Checklist:

  • Dev server works: pnpm dev
  • Build succeeds: pnpm build
  • All pages load without errors
  • No console errors
  • Theme switching works (all 6 options)
  • Mobile menu works (all sizes)
  • Responsive design (320px, 768px, 1024px, 1400px)
  • Accessibility (keyboard nav, screen reader compatible)
  • Performance (Lighthouse score > 90)

Effort: 1-2 hours
Owner: AI Assistant

Check:

  • Bundle size
  • Load time
  • Unused CSS removal
  • Image optimization
  • Alpine code splitting (if needed)

Tools:

  • Astro’s built-in performance metrics
  • Lighthouse
  • WebPageTest

Effort: 1-2 hours
Owner: AI Assistant

Update:

  • COMPONENT_LIBRARY.md - Add all new components
  • README - Update tech stack section
  • Component storybook/catalog
  • Migration guide for future developers

  • ✅ Build succeeds with zero errors
  • ✅ Dev server runs smoothly
  • Dev build time < 5 seconds
  • Production build time < 15 seconds
  • 90%+ of components are Astro
  • Alpine used only for necessary interactivity
  • Zero Svelte components (unless justified)
  • Clean file organization
  • All pages render correctly
  • Theme switching works (6 options)
  • Mobile menu functions smoothly
  • Text containers properly constrained (25rem-80rem)
  • No build warnings
  • Bundle size < 50KB (excluding images)
  • Largest Contentful Paint < 2.5s
  • Cumulative Layout Shift < 0.1
  • Lighthouse score > 90 (all metrics)
  • Responsive at all breakpoints
  • Accessible (WCAG AA compliant)
  • Keyboard navigable
  • Screen reader compatible

  1. Test after each component: Don’t batch all conversions
  2. Verify build: Run pnpm build frequently
  3. Document decisions: Add comments explaining why Astro vs Alpine
  4. Keep it simple: Don’t over-engineer components
  1. Alpine learning curve - But very quick to learn
  2. Component state management - Use simple Alpine objects
  3. CSS specificity - Use Astro’s scoped styles
  • Keep git history clean
  • Commit after each phase completes
  • Tag stable versions

PhaseDurationStatus
Phase 1: Foundation1 weekPending
Phase 2: Component Conversion1 weekPending
Phase 3: Enhance Pages1 weekPending
Phase 4: Content & Features1 weekPending
Phase 5: Testing & Optimization3-5 daysPending
Total2-3 weeksNot Started

When choosing between Astro/Alpine/Svelte:

Section titled “When choosing between Astro/Alpine/Svelte:”
Component needs to change?
├─ NO → Use Astro
└─ YES →
Change only from user interaction?
├─ YES → Alpine
└─ NO (real-time/complex) → Svelte (last resort)
Alpine sufficient?
├─ YES → Use Alpine
└─ NO (complex state) → Svelte

<!-- State -->
<div x-data="{ open: false }">
<button @click="open = !open">Toggle</button>
</div>
<!-- Conditional Rendering -->
<div x-show="open">Shown when open is true</div>
<template x-if="open"><div>DOM not created if false</div></template>
<!-- Text Content -->
<div x-text="message">Default text</div>
<!-- Attributes -->
<div :aria-label="`Item ${count}`"></div>
<!-- Classes -->
<div :class="{ active: isActive }"></div>
<!-- Listeners -->
<button @click="doSomething()" @focus="onFocus()"></button>
<!-- Initialization -->
<div x-init="() => console.log('Ready')"></div>
<!-- Watch -->
<div x-data="{ count: 0 }" x-init="$watch('count', () => console.log('Changed'))"></div>

  • User reviews and approves roadmap
  • Timeline is realistic
  • Success criteria are clear
  • Team understands philosophy
  • Begin Phase 1

Date Approved: _______________
Approved By: _______________