Skip to content
Motion
FreeIn reviewv1.0.0

About Story

The business in its own words: a tall photo with a caption beside the story and a few honest details.

npx shadcn@latest add @vibebox/about-story

Controls

Variant
<AboutStory />

About

Rebuilds the site kit's about section. Free default for About in every template.

Use it when

  • The business has a story worth a paragraph.
  • There is a photo of the team or the place, or there will be.

Skip it when

  • The story is one line: put it in the hero's intro.

Install

  1. 1Add the VibeBox registry to components.json:
    {
      "registries": {
        "@vibebox": "https://motion.vibeboxph.com/r/{name}.json"
      }
    }
  2. 2Add the block with the shadcn CLI:
    npx shadcn@latest add @vibebox/about-story
  3. 3Use it:
    import { AboutStory } from "@/components/vibebox/about-story";
    import { home } from "@/content/pages/home";
    
    <AboutStory content={home.about} variant="media-left" id="about" />

Code

2 files
components/vibebox/about-story/index.tsx
/**
 * VibeBox Motion · About Story
 * Copyright (c) 2026 VibeBox. Licensed under the VibeBox Motion Free License:
 * https://motion.vibeboxph.com/license
 * Use it in unlimited personal, commercial and client projects.
 * Do not sell or redistribute it as a component, kit, template or builder.
 */
import { Container, headingId, Media, REVEAL, RichTextView, Section, SectionHead, SlotText, type BlockProps } from "@/lib/vibebox/block-kit";
import type { Image } from "@/lib/vibebox/content";
import { cn } from "@/lib/utils";
import type { Content, Variant } from "./schema";

/*
 * Who the business is, in its own words: a tall photo with its caption beside the story and a
 * few details (who it serves, what it is known for, its hours as a fill-in until given).
 */

const FALLBACK: Image = { src: "scene:horizon", alt: "", width: 1200, height: 1500 };

export type AboutStoryProps = BlockProps<Content, Variant>;

export function AboutStory({ content, variant = "media-left", id, tone, className }: AboutStoryProps) {
  return (
    <Section slug="about-story" variant={variant} id={id} tone={tone} labelledBy={headingId(id)} className={className}>
      <Container className="grid grid-cols-[minmax(0,1fr)] items-center gap-[clamp(36px,5vw,80px)] md:grid-cols-[minmax(0,0.9fr)_minmax(0,1.1fr)]">
        <figure className={cn("m-0 grid gap-3", variant === "media-right" && "md:order-2", REVEAL)}>
          <div className="relative aspect-[4/5] overflow-hidden rounded-xl ring-1 ring-border">
            <Media image={content.media ?? FALLBACK} sizes="(min-width: 768px) 40vw, 100vw" />
            <span aria-hidden="true" className="absolute right-4 bottom-4 size-12 rounded-lg bg-spectrum shadow-sm ring-4 ring-background/60" />
          </div>
          <SlotText as="figcaption" value={content.caption} path="caption" className="text-small text-muted-foreground" />
        </figure>
        <div className="grid content-center gap-7">
          <SectionHead eyebrow={content.eyebrow} heading={content.heading} id={id} />
          <RichTextView value={content.body} path="body" className={cn("max-w-[60ch] text-[1.075rem] leading-relaxed text-pretty text-muted-foreground", REVEAL)} />
          {content.details.length > 0 && (
            <dl className={cn("grid gap-x-8 gap-y-5 border-t border-border pt-6 sm:grid-cols-2", REVEAL)}>
              {content.details.map((detail, index) => (
                <div key={index} className="grid min-w-0 gap-1">
                  <SlotText as="dt" value={detail.label} path={`details.${index}.label`} className="text-small text-muted-foreground" />
                  <SlotText as="dd" value={detail.value} path={`details.${index}.value`} className="m-0 font-medium" />
                </div>
              ))}
            </dl>
          )}
        </div>
      </Container>
    </Section>
  );
}
components/vibebox/about-story/schema.ts
/**
 * VibeBox Motion · About Story
 * Copyright (c) 2026 VibeBox. Licensed under the VibeBox Motion Free License:
 * https://motion.vibeboxph.com/license
 * Use it in unlimited personal, commercial and client projects.
 * Do not sell or redistribute it as a component, kit, template or builder.
 */
import { sections, text, type z } from "@/lib/vibebox/content";

export const variants = ["media-left", "media-right"] as const;

export type Variant = (typeof variants)[number];

export const content = sections.about.extend({
  caption: text({ role: "Photo caption", max: 60, fillIn: true }).optional(),
});

export type Content = z.infer<typeof content>;

Content

Typed slots from schema.ts. Facts (hours, prices, quotes, names) come only from the business; until then they render as fill-ins.

SlotLabelTypeLimitFact
eyebrowKickertext32 chars—
headingHeadingtext60 chars—
bodyStoryrichText600 chars—
mediaPhotoimage——
detailsDetailslistup to 4—
details[].labelLabeltext24 chars—
details[].valueValuetext60 charsCan be a fill-in
captionPhoto captiontext60 charsCan be a fill-in

Variants

  • Photo leftdefault

    media-left

    Photo on the left, story on the right.

  • Photo right

    media-right

    Story on the left, photo on the right.

Accessibility and motion

Keyboard
Any links inside the story.
Screen readers
A heading, the story, a description list of details and the figure with its caption.
Touch
Nothing to touch.
Reduced motion
The photo and story appear without their reveal.

Edge cases and limits

  • A 600-character story fits beside the photo at 1440 px; on phones it follows it.
  • No photo: a placeholder scene.
  • A tall window scene beside a bakery's story and a timeline of milestones

    A tall photo stays in view while the story, details and milestones scroll past it, each revealing as it arrives.

    About block · Server

  • A split hero: a headline and two buttons beside arches with a booking card and a treatment list

    The promise beside a stage: your photo or a scene with two floating cards that show what booking or ordering looks like.

    Heroes block · three.js

  • A headline about moving better beside a mosaic of a tile scene, a bloom scene and a class list

    The promise beside a three-tile mosaic: a tall photo, a second image and a card of what the business offers.

    Heroes block · Server

↑ ↓ to move↵ to openesc to close