Product guide · Patterns

Patterns

These recipes solve recurring interface problems without hiding the markup. Start with a pattern, then keep only the pieces your content needs.

Composition recipes

Each recipe links to the component that carries the main interaction or content structure.

Linked project index

A collection page should expose real links in its initial markup. The card contains one destination, so its title and summary describe that page.

project-index.tsx
import { Card } from "@chitrank2050/monoline-ui/card"

export function ProjectIndex({ projects }) {
  return (
    <section aria-labelledby="projects-title">
      <h2 id="projects-title">Selected projects</h2>
      <div className="grid gap-4 md:grid-cols-2">
        {projects.map((project) => (
          <Card key={project.slug} href={`/projects/${project.slug}`}>
            <Card.Body>
              <Card.Header>
                <Card.Eyebrow>{project.year}</Card.Eyebrow>
                <Card.Title>{project.title}</Card.Title>
                <Card.Description>{project.summary}</Card.Description>
              </Card.Header>
            </Card.Body>
          </Card>
        ))}
      </div>
    </section>
  )
}

Async collection states

Loading, empty, error, and success are separate states. Give each one useful copy instead of leaving an unexplained blank region.

project-results.tsx
return error ? (
  <Callout variant="warn" label="Projects unavailable">
    Try again in a moment.
  </Callout>
) : loading ? (
  <div aria-label="Loading projects">
    <Skeleton className="h-40" />
  </div>
) : projects.length === 0 ? (
  <Callout variant="note">No projects match these filters.</Callout>
) : (
  <ProjectGrid projects={projects} />
)