mdxui

Page Component

Generic content page layout with title and description

Page Component

The <Page> component provides a simple, flexible layout for generic content pages like About, Contact, Privacy Policy, or any standalone content.

Interface

import type { PageProps } from 'mdxui'

interface PageProps {
  /** Page title */
  title?: string
  /** Page description */
  description?: string
  /** Page content */
  children: ReactNode
}

Usage

Basic Example

import { Page } from '@mdxui/beacon'

<Page title="About Us" description="Learn more about our company">
  <section className="prose lg:prose-xl">
    <p>We are a company that builds amazing products...</p>
    <h2>Our Mission</h2>
    <p>To make the world a better place...</p>
  </section>
</Page>

MDX Usage

Perfect for MDX content pages:

import { Page } from '@mdxui/beacon'

<Page title="Privacy Policy" description="How we handle your data">

# Privacy Policy

Last updated: January 2025

## Information We Collect

We collect the following types of information...

## How We Use Your Information

Your information is used to...

</Page>

Simple Content Page

<Page title="Contact Us" description="Get in touch with our team">
  <div className="container max-w-2xl py-12">
    <form className="space-y-6">
      <Input name="name" label="Name" />
      <Input name="email" label="Email" type="email" />
      <Textarea name="message" label="Message" />
      <Button type="submit">Send Message</Button>
    </form>
  </div>
</Page>

Without Header

For pages that manage their own layout:

<Page>
  <section className="py-24">
    <div className="container">
      <h1 className="text-4xl font-bold">Custom Layout</h1>
      <p>Your content here...</p>
    </div>
  </section>
</Page>

Props

title (optional)

Type: string

Page title displayed at the top of the page. If provided, renders as an h1 heading.

<Page title="About Our Company">
  {/* Page content */}
</Page>

Rendered output:

<h1 className="text-4xl font-bold tracking-tight">
  About Our Company
</h1>

description (optional)

Type: string

Page description displayed below the title. Provides context for the page content.

<Page
  title="Privacy Policy"
  description="How we collect, use, and protect your personal information"
>
  {/* Page content */}
</Page>

Rendered output:

<p className="text-xl text-muted-foreground">
  How we collect, use, and protect your personal information
</p>

children (required)

Type: ReactNode

Page content. Can be any valid React elements or MDX content.

<Page title="About">
  <section>
    <p>Company history...</p>
  </section>
  <section>
    <p>Team members...</p>
  </section>
</Page>

Implementation Details

The reference implementation from @mdxui/beacon includes:

Features

  • Flexible Layout - Minimal wrapper allows custom content structure
  • Semantic HTML - Uses <main> and <article> elements
  • Typography Support - Works with prose classes for MDX content
  • Container Management - Optional container for consistent max-width
  • Responsive Spacing - Adaptive padding for mobile and desktop

Structure

<main className="flex-1">
  <article className="container py-8 md:py-12 lg:py-16">
    {title && (
      <header className="mb-8 space-y-4">
        <h1 className="text-4xl font-bold tracking-tight md:text-5xl">
          {title}
        </h1>
        {description && (
          <p className="text-xl text-muted-foreground">
            {description}
          </p>
        )}
      </header>
    )}
    <div className="max-w-none">
      {children}
    </div>
  </article>
</main>

Spacing Scale

Consistent vertical rhythm:

// Outer padding (main)
className="py-8 md:py-12 lg:py-16"

// Header margin (if title present)
className="mb-8"

// Mobile: 2rem (32px)
// Tablet: 3rem (48px)
// Desktop: 4rem (64px)

Common Use Cases

About Page

<Page title="About Us" description="Our story and mission">
  <div className="prose lg:prose-xl">
    <h2>Our Story</h2>
    <p>
      Founded in 2020, we set out to solve a common problem...
    </p>

    <h2>Our Team</h2>
    <div className="grid grid-cols-3 gap-6 not-prose">
      {teamMembers.map(member => (
        <TeamMemberCard key={member.id} {...member} />
      ))}
    </div>

    <h2>Our Values</h2>
    <ul>
      <li>Customer First</li>
      <li>Innovation</li>
      <li>Transparency</li>
    </ul>
  </div>
</Page>

Contact Page

<Page title="Contact Us" description="We'd love to hear from you">
  <div className="grid md:grid-cols-2 gap-12">
    <div>
      <h2 className="text-2xl font-bold mb-4">Send us a message</h2>
      <ContactForm />
    </div>
    <div>
      <h2 className="text-2xl font-bold mb-4">Other ways to reach us</h2>
      <div className="space-y-4">
        <ContactMethod
          icon={<Mail />}
          label="Email"
          value="hello@example.com"
        />
        <ContactMethod
          icon={<Phone />}
          label="Phone"
          value="+1 (555) 123-4567"
        />
        <ContactMethod
          icon={<MapPin />}
          label="Address"
          value="123 Main St, San Francisco, CA"
        />
      </div>
    </div>
  </div>
</Page>

Privacy Policy

<Page title="Privacy Policy" description="Last updated: January 2025">
  <div className="prose lg:prose-lg max-w-4xl mx-auto">
    <h2>1. Information We Collect</h2>
    <p>
      We collect information you provide directly to us when you:
    </p>
    <ul>
      <li>Create an account</li>
      <li>Make a purchase</li>
      <li>Contact customer support</li>
    </ul>

    <h2>2. How We Use Your Information</h2>
    <p>
      We use the information we collect to:
    </p>
    <ul>
      <li>Provide, maintain, and improve our services</li>
      <li>Process transactions and send related information</li>
      <li>Send you technical notices and support messages</li>
    </ul>

    <h2>3. Information Sharing</h2>
    <p>
      We do not share your personal information except in the following cases...
    </p>
  </div>
</Page>

Terms of Service

<Page title="Terms of Service" description="Please read these terms carefully">
  <div className="prose max-w-4xl mx-auto">
    <section>
      <h2>1. Acceptance of Terms</h2>
      <p>
        By accessing and using this service, you accept and agree to be bound
        by the terms and provision of this agreement...
      </p>
    </section>

    <section>
      <h2>2. Use License</h2>
      <p>
        Permission is granted to temporarily download one copy of the materials...
      </p>
    </section>

    <section>
      <h2>3. Disclaimer</h2>
      <p>
        The materials on this website are provided on an 'as is' basis...
      </p>
    </section>
  </div>
</Page>

Changelog Page

<Page title="Changelog" description="Recent updates and improvements">
  <div className="max-w-3xl mx-auto space-y-12">
    <ChangelogEntry
      version="2.0.0"
      date="January 15, 2025"
      changes={[
        { type: "feature", text: "New dashboard redesign" },
        { type: "feature", text: "Dark mode support" },
        { type: "fix", text: "Fixed login redirect issue" },
      ]}
    />
    <ChangelogEntry
      version="1.9.0"
      date="December 20, 2024"
      changes={[
        { type: "feature", text: "Export to CSV" },
        { type: "improvement", text: "Faster page loads" },
      ]}
    />
  </div>
</Page>

FAQ Page

<Page title="Frequently Asked Questions" description="Find answers to common questions">
  <div className="max-w-3xl mx-auto">
    <Accordion type="single" collapsible>
      {faqItems.map((item, index) => (
        <AccordionItem key={index} value={`item-${index}`}>
          <AccordionTrigger>{item.question}</AccordionTrigger>
          <AccordionContent>{item.answer}</AccordionContent>
        </AccordionItem>
      ))}
    </Accordion>
  </div>
</Page>

Typography with Prose

The Page component works well with Tailwind Typography (@tailwindcss/typography):

<Page title="Blog Post Title">
  <div className="prose lg:prose-xl dark:prose-invert mx-auto">
    {/* MDX content with automatic styling */}
    <p>Paragraph text is automatically styled...</p>
    <h2>Subheadings are styled</h2>
    <ul>
      <li>Lists are styled</li>
      <li>With proper spacing</li>
    </ul>
    <blockquote>
      Blockquotes get proper styling too
    </blockquote>
  </div>
</Page>

Prose modifiers:

  • prose-sm - Smaller text
  • prose-lg - Larger text
  • prose-xl - Extra large text
  • dark:prose-invert - Dark mode support
  • not-prose - Opt-out specific elements

Composition with Site

The <Page> component works with <Site> for full page layout:

<Site name="Acme">
  <Page title="About" description="Learn about our company">
    <section className="prose">
      {/* Content */}
    </section>
  </Page>
</Site>

Full page structure:

<Site>
  <Header />   {/* Provided by Site */}
  <Page>       {/* Main content */}
    {children}
  </Page>
  <Footer />   {/* Provided by Site */}
</Site>

Accessibility

The reference implementation includes:

  • Semantic HTML - Uses <main> and <article> elements
  • Heading Hierarchy - Title is h1, content starts at h2
  • Landmark Regions - Proper ARIA landmarks for navigation
  • Skip Links - Support for "skip to content"
  • Focus Management - Keyboard navigation support
<main id="main-content" role="main">
  <article>
    <header>
      <h1>{title}</h1>
      <p>{description}</p>
    </header>
    <div>{children}</div>
  </article>
</main>

Comparison: Page vs LandingPage

Use <Page> for:

  • About, Contact, Privacy pages
  • Legal documents (Terms, Privacy)
  • Simple content pages
  • MDX blog posts or articles
  • Documentation pages

Use <LandingPage> for:

  • Marketing homepages
  • Product launches
  • Conversion-focused pages
  • Multi-section landing pages
  • Pages with Hero, Features, Pricing sections

TypeScript

Full type definitions:

import type { ReactNode } from 'react'
import type { z } from 'zod'
import type { Component } from 'mdxui'

export const PagePropsSchema = z.object({
  title: z.string().optional(),
  description: z.string().optional(),
  children: z.any(),
})

export type PageProps = z.infer<typeof PagePropsSchema>
export type Page = Component<PageProps>

On this page