Skip to main content

Component Architecture

Astro components use a unique .astro format that combines:
  • Frontmatter (TypeScript/JavaScript)
  • Template (HTML-like JSX)
  • Scoped styles (CSS)
  • Client-side scripts

Creating a New Component

1

Create the Component File

Components live in src/components/. Create a new file:
touch src/components/MyComponent.astro
For property-related components, use the subdirectory:
touch src/components/properties/MyPropertyComponent.astro
2

Define the Component Structure

Here’s a basic template:
---
// Frontmatter: TypeScript logic, imports, props
interface Props {
  title: string;
  subtitle?: string;
}

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

<div class="my-component">
  <h2>{title}</h2>
  {subtitle && <p>{subtitle}</p>}
</div>

<style>
  .my-component {
    padding: var(--spacing-section, 4rem);
    background: var(--color-1);
  }
  
  h2 {
    font-family: var(--font-heading);
    color: var(--color-4);
  }
</style>
3

Import and Use the Component

Import your component in any .astro page or layout:
---
import MyComponent from '../components/MyComponent.astro';
---

<MyComponent title="Welcome" subtitle="Subtitle text" />
Let’s examine the existing Footer.astro component as a reference: Location: src/components/Footer.astro
---
import {
  getLangFromUrl,
  useTranslations,
  useTranslatedPath,
} from "../i18n/utils";

const lang = getLangFromUrl(Astro.url);
const t = useTranslations(lang);
const translatePath = useTranslatedPath(lang);
const currentYear = new Date().getFullYear();
---

<footer class="footer">
  <div class="footer-container">
    <div class="footer-separator"></div>
    
    <div class="footer-content">
      <div class="footer-block links-block">
        <nav class="footer-nav">
          <ul class="nav-list">
            <li><a href={translatePath("/")}>{t("nav.home")}</a></li>
            <li><a href={translatePath("/propiedades")}>{t("nav.properties")}</a></li>
          </ul>
        </nav>
      </div>
    </div>
    
    <div class="copyright">
      &copy; {currentYear} Adosa.
    </div>
  </div>
</footer>
Key features:
  • Uses i18n utilities for translations
  • Computes currentYear dynamically
  • Scoped styles (not shown for brevity)
See src/components/Footer.astro:1-100 for the full implementation.

Styling Patterns

Using CSS Custom Properties

The project uses consistent CSS variables defined in src/styles/global.css:
:root {
  --color-1: #FFFFFF;        /* Primary background */
  --color-4: #1A1A1A;        /* Dark text/elements */
  --font-heading: 'Onest', sans-serif;
  --font-body: 'Onest', sans-serif;
  --spacing-section: 4rem;
  --spacing-container: 4rem;
  --height-nav: 90px;
}
Usage in components:
<style>
  .my-section {
    padding: var(--spacing-section);
    background: var(--color-1);
    color: var(--color-4);
  }
</style>

Responsive Design

Follow the existing breakpoint conventions:
/* Desktop-first approach */
.component {
  font-size: 2rem;
}

/* Tablet */
@media (max-width: 1023px) {
  .component {
    font-size: 1.5rem;
  }
}

/* Mobile */
@media (max-width: 767px) {
  .component {
    font-size: 1.2rem;
  }
}
The Navigation component at src/components/Navigation.astro:352-378 shows excellent responsive patterns including hamburger menu implementation.

Adding Client-Side Interactivity

For components that need JavaScript, use <script> tags:
---
interface Props {
  items: string[];
}
const { items } = Astro.props;
---

<div class="interactive-list">
  {items.map((item, i) => (
    <button class="list-item" data-index={i}>{item}</button>
  ))}
</div>

<script>
  const buttons = document.querySelectorAll('.list-item');
  
  buttons.forEach((btn) => {
    btn.addEventListener('click', (e) => {
      const target = e.currentTarget as HTMLElement;
      const index = target.dataset.index;
      console.log(`Clicked item ${index}`);
    });
  });
</script>
Scripts in .astro components run once per page load. For dynamic content, consider using framework components (React, Vue) with client:load directive.

Animation Integration

Add scroll-triggered animations using the project’s utilities:
---
// Import animation helper
---

<div class="animated-section">
  <h2 class="text-reveal">This text will reveal on scroll</h2>
  <p class="text-reveal">So will this paragraph</p>
</div>

<script>
  import { initTextRevealAnimation } from '../utils/textRevealAnimation';
  
  // Initialize after page is ready
  window.addEventListener('page-ready', () => {
    initTextRevealAnimation('.animated-section', {
      stagger: 0.15,
      start: 'top 85%',
      duration: 1.2
    });
  });
</script>
See the Customizing Animations guide for detailed animation patterns.

Component Props Patterns

Required vs Optional Props

---
interface Props {
  title: string;              // Required
  subtitle?: string;          // Optional
  items?: string[];           // Optional with default
}

const { 
  title, 
  subtitle,
  items = [] 
} = Astro.props;
---

Complex Props (Example: Property Component)

---
import type { Property } from '../types';

interface Props {
  property: Property;
  showDetails?: boolean;
}

const { property, showDetails = false } = Astro.props;
---

<div class="property-card">
  <img src={property.image} alt={property.title} />
  <h3>{property.title}</h3>
  <p>{property.location}</p>
  
  {showDetails && (
    <div class="details">
      <span>{property.bedrooms} beds</span>
      <span>{property.bathrooms} baths</span>
    </div>
  )}
</div>
See src/types/index.ts:74-77 for the PropertyCardProps interface.

Reusable Component Examples

The project includes several reusable components you can reference:

Navigation

Location: src/components/Navigation.astroFeatures:
  • Desktop and mobile layouts
  • Hamburger menu animation
  • Language switcher
  • Auto-hide on scroll
Lines: src/components/Navigation.astro:1-469

ScrollGallery

Location: src/components/ScrollGallery.astroFeatures:
  • Scroll-hijacking gallery
  • Progress indicators
  • Configurable text position
  • Responsive layout
Lines: src/components/ScrollGallery.astro:1-80

ShareButtons

Location: src/components/ShareButtons.astroFeatures:
  • Social media sharing
  • WhatsApp integration
  • Copy link functionality
  • QR code generation

EmptySection

Location: src/components/EmptySection.astroFeatures:
  • Simple placeholder component
  • Minimal styling
  • Reusable pattern

Best Practices

Keep components focused and single-purpose. If a component exceeds 200 lines, consider breaking it into smaller sub-components.
Always define Props interfaces. This provides autocomplete and type safety:
interface Props {
  title: string;
  count: number;
}
Prefer scoped styles in <style> tags over global CSS. This prevents style conflicts between components.
Include proper ARIA labels and semantic HTML:
<button aria-label="Close menu" class="close-btn">
  <span>×</span>
</button>

Testing Your Component

1

Visual Testing

Add your component to a test page and run the dev server:
npm run dev
Navigate to the page and verify appearance.
2

Responsive Testing

Use browser DevTools device emulation to test different screen sizes:
  • Desktop (greater than 1024px)
  • Tablet (768px - 1023px)
  • Mobile (less than 768px)
3

Build Testing

Verify your component works in production build:
npm run build && npm run preview

Next Steps

Working with Properties

Learn about the Property interface and data fetching

Customizing Animations

Add scroll-triggered animations to your components

Build docs developers (and LLMs) love