Skip to main content
Components are reusable pieces of UI that can be imported and used across multiple pages. The project uses both Astro components (.astro) and React components (.tsx).

Component types

The website uses two types of components:

Astro components

.astro files - Server-rendered components with no client-side JavaScript by default

React components

.tsx files - Interactive components with client-side state and event handlers

Astro components

Astro components are the primary building blocks. They are rendered on the server and ship zero JavaScript to the client by default.

Available Astro components

The src/components/ directory contains:
  • AccessibilityMenu.astro - Accessibility features menu
  • AlertBanner.astro - Site-wide alert banner
  • AlertSmall.astro - Compact alert messages
  • CookieBanner.astro - Cookie consent banner
  • DonationForm.astro - Interactive donation form
  • ExternalLinkWarning.astro - Warning for external links
  • LocationMap.astro - Embedded map component
  • LocationPermission.astro - Location permission prompt
  • PrayerRequestForm.astro - Prayer submission form
  • PrivacyBanner.astro - Privacy notice banner
  • SeasonalBanner.astro - Seasonal announcements
  • SelfXSS.astro - Developer console warning
  • SharePost.astro - Social sharing buttons
  • SundayCalendar.astro - Service schedule calendar
  • WeatherBanner.astro - Weather alerts

Example: Prayer request form

Here’s how the PrayerRequestForm.astro component is structured:
src/components/PrayerRequestForm.astro
---
interface Props {
  title?: string;
  description?: string;
  showAnonymous?: boolean;
}

const {
  title = "Prayer Requests",
  description = "Share your prayer needs with our community.",
  showAnonymous = true
} = Astro.props;
---

<div class="prayer-request-section">
  <div class="grid lg:grid-cols-2 gap-16 items-start">
    <div class="bg-gray-50 rounded-2xl p-8">
      <form id="prayer-request-form" method="POST">
        <div>
          <label for="prayer-name">
            Your Name {showAnonymous && <span>(optional)</span>}
          </label>
          <input type="text" id="prayer-name" name="name" />
        </div>
        
        <div>
          <label for="prayer-request">Prayer Request *</label>
          <textarea id="prayer-request" name="request" required />
        </div>
        
        <button type="submit">Submit Prayer Request</button>
      </form>
    </div>
    
    <div>
      <h2>{title}</h2>
      <p>{description}</p>
    </div>
  </div>
</div>

<script>
  // Client-side form handling
  document.addEventListener('DOMContentLoaded', () => {
    const form = document.getElementById('prayer-request-form');
    
    form?.addEventListener('submit', async (e) => {
      e.preventDefault();
      // Handle form submission
    });
  });
</script>

Component props

Astro components accept props through the Astro.props object:
---
interface Props {
  title?: string;
  description?: string;
  showAnonymous?: boolean;
}

const { title, description, showAnonymous = true } = Astro.props;
---

Using Astro components

Import and use components in your pages:
src/pages/index.astro
---
import Layout from '../layouts/Layout.astro';
import PrayerRequestForm from '../components/PrayerRequestForm.astro';
import DonationForm from '../components/DonationForm.astro';
---

<Layout title="Welcome">
  <section id="prayer">
    <PrayerRequestForm />
  </section>
  
  <section id="donate">
    <DonationForm id="homepage" />
  </section>
</Layout>

React components

React components are used for interactive features that require client-side JavaScript.

Example: Salvation Marquee

The SalvationMarquee.tsx component displays scrolling testimonials:
src/components/SalvationMarquee.tsx
import { useState } from 'react';
import { Marquee } from '@joycostudio/marquee/react';

interface Testimony {
  name: string;
  quote: string;
  year?: string;
}

interface Props {
  testimonies?: Testimony[];
}

const defaultTestimonies: Testimony[] = [
  {
    name: "Sarah M.",
    quote: "God's grace transformed my life when I thought all hope was lost.",
    year: "2023"
  },
  // More testimonies...
];

export default function SalvationMarquee({ testimonies = defaultTestimonies }: Props) {
  const [isPaused, setIsPaused] = useState(false);
  const duplicatedTestimonies = [...testimonies, ...testimonies];

  return (
    <div 
      className="salvation-marquee-wrapper"
      onMouseEnter={() => setIsPaused(true)}
      onMouseLeave={() => setIsPaused(false)}
    >
      <Marquee speed={30} direction={-1} play={!isPaused}>
        {duplicatedTestimonies.map((testimony, index) => (
          <div key={`${testimony.name}-${index}`} className="mx-3">
            <p>{testimony.quote}</p>
            <span>{testimony.name}</span>
          </div>
        ))}
      </Marquee>
    </div>
  );
}

Using React components

React components must be explicitly hydrated on the client using directives:
src/pages/index.astro
---
import SalvationMarquee from '../components/SalvationMarquee';
---

<section id="salvation-stories">
  <SalvationMarquee client:load />
</section>
The client:load directive tells Astro to hydrate this component immediately on page load.

Client directives

Control when React components load:
<SalvationMarquee client:load />
Loads and hydrates immediately on page load.

Component patterns

Props and TypeScript

Define component props with TypeScript interfaces:
---
interface Props {
  id?: string;
  showHeader?: boolean;
}

const { id = 'donation', showHeader = true } = Astro.props;
---

<div>
  {showHeader && <h3>Make a Donation</h3>}
  <form id={`${id}-form`}>
    <!-- Form content -->
  </form>
</div>

Client-side scripts

Add interactivity to Astro components with <script> tags:
<form id="donation-form">
  <input type="number" class="amount-input" />
  <button type="submit">Donate</button>
</form>

<script>
  const form = document.getElementById('donation-form');
  const amountInput = form?.querySelector('.amount-input');
  
  form?.addEventListener('submit', async (e) => {
    e.preventDefault();
    // Handle submission
  });
</script>
Scripts in Astro components run once when the page loads. For components that might be added/removed dynamically, use event delegation or framework components.

Conditional rendering

Use JavaScript expressions to conditionally render content:
src/pages/index.astro
---
export const prerender = false;
import { isLocalVisitor } from '../utils/locationCheck';

let isLocal = false;
try {
  isLocal = await isLocalVisitor();
} catch (error) {
  isLocal = false;
}
---

{isLocal ? (
  <p>Welcome, neighbor!</p>
) : (
  <p>Plan Your Visit</p>
)}

Component organization

1

Choose the right type

  • Use Astro components for static content and server-rendered UI
  • Use React components for interactive features with state
2

Define clear props

interface Props {
  title: string;        // Required
  description?: string; // Optional
  count?: number;       // Optional with type
}
3

Import and use

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

<MyComponent title="Hello" description="World" />
4

Add interactivity

  • Astro: Use <script> tags for simple interactions
  • React: Use hooks and state for complex UI

Styling components

Components can include scoped styles:
<div class="prayer-form">
  <!-- Content -->
</div>

<style>
  .prayer-form {
    font-family: 'Albert Sans', system-ui, sans-serif;
  }
  
  /* Styles are scoped to this component */
</style>
For React components, use className:
export default function SalvationMarquee() {
  return (
    <div className="salvation-marquee-wrapper bg-white overflow-hidden">
      {/* Content */}
    </div>
  );
}

Next steps

Layouts

Learn about the layout system

Pages & routing

Understand Astro’s routing

Build docs developers (and LLMs) love