Skip to content
Esc
↑↓navigate↵open⌘Jpreview
On this page

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.

Was this page helpful?