Instruction file imported from justdesigned/portfolio (
.github/instructions/astro-components.instructions.md). Copyright stays with the author.
Astro Component (.astro) Instructions
File Structure
Astro components use triple-dash separator (---) to divide frontmatter from template:
---
// Frontmatter: TypeScript, imports, logic
import Layout from '../layouts/Layout.astro';
interface Props {
title: string;
}
const { title } = Astro.props;
const computedValue = Math.random();
---
<!-- HTML template with Tailwind classes -->
<div class="rounded-lg border border-slate-200 bg-slate-50 p-4">
<h1 class="text-2xl font-bold text-slate-900">{title}</h1>
<p class="text-slate-600">{computedValue}</p>
</div>
Props
Define component properties with TypeScript interfaces:
---
interface Props {
title: string;
description?: string; // Optional
count?: number;
}
const { title, description, count = 0 } = Astro.props;
---
<div>
<h2>{title}</h2>
{description && <p>{description}</p>}
<span>Count: {count}</span>
</div>
Slots
Accept children content via <slot />:
---
interface Props {
heading: string;
}
const { heading } = Astro.props;
---
<div class="rounded-lg border border-gray-200 bg-white p-6 shadow-md">
<h3 class="text-lg font-semibold text-gray-900">{heading}</h3>
<slot />
<!-- Child content rendered here -->
</div>
Named Slots
Use named slots for multiple content areas:
---
// Card.astro
---
<div class="card">
<header>
<slot name="header" />
</header>
<main>
<slot />
<!-- Default slot -->
</main>
<footer>
<slot name="footer" />
</footer>
</div>
Usage:
---
import Card from './Card.astro';
---
<Card>
<h2 slot="header">Title</h2>
<p>Main content</p>
<button slot="footer">Action</button>
</Card>
Conditional Rendering
---
const { showBanner, items = [] } = Astro.props;
---
{showBanner && <div class="banner">Welcome!</div>}
{
items.length > 0 ? (
<ul>
{items.map((item) => (
<li>{item}</li>
))}
</ul>
) : (
<p>No items found</p>
)
}
Astro.props Special Properties
---
const { class: className, ...rest } = Astro.props;
---
<div class:list={['base-styles', className]} {...rest}>
<slot />
</div>
Class List Utility
Dynamic classes with class:list:
---
const { variant = 'primary', active = false } = Astro.props;
---
<button
class:list={[
'rounded px-4 py-2',
{
'bg-blue-500 text-white': variant === 'primary',
'bg-gray-200 text-gray-900': variant === 'secondary',
},
active && 'ring-2 ring-blue-400',
]}
>
<slot />
</button>
Run linters:
pnpm lint:eslint # Check for issues
pnpm lint:eslint:fix # Auto-fix issues
Formatting
Prettier automatically formats Astro files with:
- Consistent indentation (tabs)
- Single quotes for strings
- Semicolons enabled
- 80-character line width
Format files:
pnpm format # Format all files
pnpm format:check # Check formatting
Best Practices
- Props: Always define TypeScript interfaces
- Imports: Group by type (external, internal, components)
- Accessibility: Use semantic HTML and ARIA attributes
- Performance: Minimize client-side JavaScript
- Styling: Prefer Tailwind classes over scoped CSS
- Consistent design tokens
- Smaller final CSS bundle
Client-Side Scripts
Use <script> tags for client-side JavaScript:
<div id="interactive">Click me</div>
<script>
document.querySelector('#interactive')?.addEventListener('click', () => {
console.log('Clicked!');
});
</script>
Best Practices
- Prefer Tailwind CSS: Use utility classes for styling over
<style>blocks - Keep pure components: Focus on structure and presentation
- Use props for configuration: Make components reusable
- Minimize client scripts: Let Astro handle most rendering
- Use layouts: Wrap pages with consistent templates
- Type everything: Use TypeScript interfaces for Props
- Responsive design: Use Tailwind breakpoints (
md:,lg:) for responsive layouts
Common Patterns
Conditional Rendering
{condition && <p>Shown when true</p>}
{condition ? <p>True</p> : <p>False</p>}
Loops
{items.map((item) => <li key={item.id}>{item.name}</li>)}
Importing Components
import Button from '../components/Button.astro'; import ReactCounter from
'../components/ReactCounter.tsx';
<Button />
<ReactCounter client:load />
Default Props
---
interface Props {
size?: 'sm' | 'md' | 'lg';
variant?: 'primary' | 'secondary';
}
const { size = 'md', variant = 'primary' } = Astro.props;
---