Skip to content

Passing Props to Islands

Passing props to a React island looks identical to passing props in JSX — the syntax is the same. There is one important constraint though: props passed across the Astro/island boundary must be serializable to JSON.

Astro renders your page on the server. When it encounters a hydrated island, it serializes the props into the HTML so the browser can rehydrate the component with the exact same data.

That means props must be values JSON can represent:

  • Strings, numbers, booleans
  • Plain objects and arrays (no class instances)
  • null and undefined

Not serializable: functions, Dates (use ISO strings instead), class instances, React nodes, DOM elements.

React
// React app — any prop type works,
// including functions passed from a parent.
<Counter
start={10}
label="Score"
onReset={() => console.log('reset')}
date={new Date()}
/>
Astro
---
// Astro — props must be JSON-serializable.
// Functions and class instances are NOT allowed
// across the Astro→island boundary.
import Counter from '../components/Counter.jsx';
const iso = new Date().toISOString(); // Date → string ✓
---
<Counter
start={10}
label="Score"
date={iso}
/>
{/* onReset={() => ...} would throw a build error */}

Functions as props: You cannot pass a callback from .astro to a React island. If your island needs a callback (e.g., a modal that calls onClose), handle that interaction entirely inside the island — fetch data, manage state, and navigate using the browser’s native APIs from within React.

React passes children via the children prop. Astro uses <slot />. When you use a React island as a wrapper, you can pass HTML content to it via Astro’s slot mechanism — but the child content will be static HTML, not a React component tree.

---
import Card from '../components/Card.jsx';
---
<Card client:load title="Hello">
<p>This content is passed as a slot — static HTML inside the React component.</p>
</Card>

Inside Card.jsx, that content arrives as children exactly as you would expect:

src/components/Card.jsx
export default function Card({ title, children }) {
return (
<div className="card">
<h2>{title}</h2>
<div>{children}</div>
</div>
);
}

Astro supports named slots. Inside a React island, named slot content arrives as a prop with the slot name:

---
import Modal from '../components/Modal.jsx';
---
<Modal client:load>
<h2 slot="header">Confirm deletion</h2>
<p>This action cannot be undone.</p>
<button slot="footer">Cancel</button>
</Modal>
src/components/Modal.jsx
export default function Modal({ children, header, footer }) {
return (
<dialog>
<header>{header}</header>
<main>{children}</main>
<footer>{footer}</footer>
</dialog>
);
}
React
// React parent → React child
// Any prop type, including functions and Dates.
function Page() {
return (
<UserCard
user={userObject} // class instance ok
onEdit={() => setEdit(true)} // function ok
createdAt={new Date()} // Date ok
/>
);
}
Astro
---
// Astro page → React island
// Only JSON-serializable props allowed.
import UserCard from '../components/UserCard.jsx';
const user = await fetch('/api/user').then(r => r.json());
---
<UserCard
name={user.name}
avatarUrl={user.avatarUrl}
createdAt={user.createdAt}
/>
{/*
user object (plain JSON) ✓
ISO date string ✓
Function props ✗ — handle inside the island
*/}
Why must props passed to an Astro island be JSON-serializable?
Which of the following is NOT a valid prop to pass from .astro to a React island?
How does slot/children content passed to a React island from .astro arrive in the component?