Skip to content
Motion
FreeIn reviewv1.0.0

Contact Strip

A compact contact band: the heading, every channel on one line and one action.

npx shadcn@latest add @vibebox/contact-strip

Controls

Variant
<ContactStrip />

About

Rebuilds the site kit's compact contact strip, used on FAQ and inner pages.

Use it when

  • An inner page ends with a reminder of how to get in touch.

Skip it when

  • This is the site's main contact section: use contact-split.

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/contact-strip
  3. 3Use it:
    import { ContactStrip } from "@/components/vibebox/contact-strip";
    import { home } from "@/content/pages/home";
    
    <ContactStrip content={home.contact} variant="row" id="contact" />

Code

2 files
components/vibebox/contact-strip/index.tsx
/**
 * VibeBox Motion · Contact Strip
 * 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 { ActionLink, ChannelValue, CHANNEL_ICONS, Container, Eyebrow, headingId, Icon, REVEAL, Section, SlotText, type BlockProps } from "@/lib/vibebox/block-kit";
import { cn } from "@/lib/utils";
import type { Content, Variant } from "./schema";

/*
 * A compact contact band: the heading, every channel on one line, and one action. For the end of
 * an inner page, or above the footer when the full contact section lives elsewhere.
 */

export type ContactStripProps = BlockProps<Content, Variant>;

export function ContactStrip({ content, variant = "row", id, tone, className }: ContactStripProps) {
  const centered = variant === "centered";
  return (
    <Section slug="contact-strip" variant={variant} id={id} tone={tone} labelledBy={headingId(id)} spacing={false} className={cn("py-[clamp(40px,6vw,80px)]", className)}>
      <Container>
        <div
          className={cn(
            "grid gap-8 rounded-xl bg-card p-6 text-card-foreground ring-1 ring-border sm:p-8",
            centered ? "justify-items-center text-center" : "lg:grid-cols-[minmax(0,0.8fr)_minmax(0,1.6fr)_auto] lg:items-center lg:gap-12",
            REVEAL,
          )}
        >
          <div className={cn("grid gap-3", centered && "justify-items-center")}>
            <Eyebrow value={content.eyebrow} />
            <SlotText as="h2" value={content.heading} path="heading" id={headingId(id)} className="font-display text-[clamp(1.6rem,1.2rem+1.4vw,2.4rem)] leading-tight font-semibold tracking-[-0.025em] text-balance" />
            <SlotText as="p" value={content.lead} path="lead" className="max-w-[48ch] text-small text-pretty text-muted-foreground" />
          </div>
          <dl className={cn("grid gap-x-8 gap-y-4 sm:grid-cols-2", centered ? "w-full max-w-4xl text-left lg:grid-cols-[repeat(auto-fit,minmax(12rem,1fr))]" : "")}>
            {content.channels.map((channel, index) => (
              <div key={index} className="relative grid min-w-0 gap-0.5 pl-6.5">
                <dt data-slot={`channels.${index}.label`} className="text-caption text-muted-foreground">
                  <Icon name={CHANNEL_ICONS[channel.kind]} className="absolute top-0.5 left-0 size-4 text-muted-foreground" />
                  {channel.label}
                </dt>
                <dd className="m-0 min-w-0 text-small font-medium">
                  <ChannelValue channel={channel} path={`channels.${index}.value`} />
                </dd>
              </div>
            ))}
          </dl>
          {content.action && <ActionLink action={content.action} path="action" className="justify-self-start lg:justify-self-end" />}
        </div>
      </Container>
    </Section>
  );
}
components/vibebox/contact-strip/schema.ts
/**
 * VibeBox Motion · Contact Strip
 * 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 { action, sections, type z } from "@/lib/vibebox/content";

export const variants = ["row", "centered"] as const;

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

export const content = sections.contact.omit({ form: true, media: true }).extend({
  action: action({ role: "Button" }).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—
leadIntrotext160 chars—
channelsWays to reach youlistup to 6—
channels[].kindChannelchoice——
channels[].labelLabeltext24 chars—
channels[].valueValuefact120 charsYes, a fill-in until given
actionButtonaction24 chars—

Variants

  • Rowdefault

    row

    Heading, channels and action in one row on desktop.

  • Centred

    centered

    Centred heading, channels in a wrapping grid, action beneath.

Accessibility and motion

Keyboard
Channel links, then the action.
Screen readers
A heading and a description list of channels.
Touch
Links are full-width rows on phones.
Reduced motion
The strip appears without its reveal.

Edge cases and limits

  • No action: the strip is heading and channels.
  • Four contact cards with icons: visit, hours, call and email

    One card per way to reach the business (call, visit, message), or a list of rows; facts until given.

    Contact block · Server

  • Contact channels with icons and a map scene beside an inquiry form

    Every way to reach the business beside an inquiry form that works without JavaScript; facts are fill-ins until given.

    Contact block · Server

  • A chat panel with a greeting and three quick questions beside contact channels

    Contact as a conversation: a chat panel whose quick questions open the business's chat app, prefilled.

    Contact block · Server

↑ ↓ to move↵ to openesc to close