Custom components
Extend MDX with your own Astro and React components while preserving Brandtree's data model.
Start with built-in components. When you need another exhibit, create an author-owned component and explicitly import it in MDX. Keep reusable brand facts in the config and pass the values the component needs through props. This keeps a component usable for another client or locale.
Astro components
Create components/UsageNote.astro in your consumer project:
---
interface Props {
title: string;
}
const { title } = Astro.props;
---
<aside class="usage-note">
<strong>{title}</strong>
<slot />
</aside>
<style>
.usage-note {
border-inline-start: 2px solid var(--accent);
padding-inline-start: var(--space-m);
color: var(--ink);
background: var(--surface);
}
</style>
Import it from a page at content/en/03-logo/index.mdx:
import UsageNote from "../../../components/UsageNote.astro";
## Original artwork
<UsageNote title="Use the supplied SVG">
Keep the proportions unchanged.
</UsageNote>
Imports resolve relative to the original MDX file, not .brandtree/. Adjust the
number of ../ segments when moving a page. The Astro component guide
explains props, slots, and scoped styles.
Built-in MDX components receive shared props automatically; custom components
do not. If a custom component needs brand data, accept it as a required prop and forward
props.brand from MDX. Do the same for UI strings (props.ui) and content
direction (props.dir). These are already resolved for the rendered page;
importing the canonical config inside a component would bypass locale overrides.
Interactive React components
Astro components render HTML. For browser interaction, use a React component and an Astro client directive. Declare dependencies imported by author code directly:
pnpm add react react-dom
pnpm add -D @types/react @types/react-dom
Create components/SpecimenInput.tsx:
import { useState } from "react";
export default function SpecimenInput({
label,
initialText,
}: {
label: string;
initialText: string;
}) {
const [text, setText] = useState(initialText);
return (
<div>
<label>
{label}
<input value={text} onChange={(event) => setText(event.target.value)} />
</label>
<p style={{ fontFamily: "var(--font-display)" }}>{text}</p>
</div>
);
}
Then use it in the same nested page:
import SpecimenInput from "../../../components/SpecimenInput.tsx";
<SpecimenInput
client:load
label="Try the typeface"
initialText={props.brand.meta.tagline ?? props.brand.meta.name}
/>
client:load sends the component’s JavaScript to the browser and activates it
after the page loads. Without a client directive it renders static HTML and its
input handler does not run. Use hydration only for components that need it.
Localize the label in each locale’s page. See Astro’s
framework components guide
for directive choices and the island model.
Brandtree supplies the React integration, but that does not make its private
dependencies your project’s imports. Declare React and any other package your
own code imports. If author code imports Astro APIs such as astro:assets,
declare astro directly too. Custom dependencies resolve using normal package
exports; generated aliases do not expose private framework dependencies.
Styling your component
Use semantic CSS variables and logical properties, as in UsageNote above.
Styling and tokens covers shared CSS, theme fallbacks, text measure,
and overflow in full.
Current extension boundaries
The generated runtime automatically provides brandtree/components. It does not
currently merge an author components.ts registry. Explicit MDX imports are the
supported extension path.
The current generator also does not copy an author pages/ directory or expose
custom Astro config, middleware, integrations, or layout replacement through
validated config. Some design documents describe these as future extensions.
Do not add unsupported keys or maintain your own Astro composition inside the
consumer as a workaround. Use the supported content and component APIs, or make
a deliberate framework change when the product needs a new capability.