DesignBlocs UI Library

Pillar Panels

Three full-height panels side by side, each on its own gradient ground with a dashed numbered marker, a category and a short statement. Takes an optional looping video per panel.

  • Features
  • No dependencies
  • MIT
  • Google Sans Flex · SIL Open Font License 1.1
Preview
Open ↗
Pillar Panels — previewLoading

Copy the code

The whole component, in one file. Yours from the moment you paste it.

Install it

Straight into a project with a shadcn setup — no account, no limit.

npx shadcn@latest add https://designblocs.com/r/pillar-panels.json

Hand it to an agent

A prompt for Claude Code, Cursor or v0 that adapts the component to your codebase rather than pasting it in raw.

src/components/ui-library/blocks/PillarPanels.tsx

/**
 * PillarPanels — three full-height panels, each its own ground, read left to right.
 *
 * A numbered marker, a category, a short title and a sentence, on a gradient
 * built for that panel. Each panel can take a looping background video; without
 * one the gradient is the design rather than a placeholder, which is why the
 * component ships no media of its own — supply a `video` when you have one.
 *
 * At desk widths the three sit side by side, divided by hairlines, and the row
 * is tall enough to be a chapter rather than a strip. Below 1024px they stack,
 * and the divider moves from the left edge to the top.
 *
 * The heading exists for the document outline but is visually hidden: the
 * panels are the heading, and a second one above them would be noise.
 *
 * SELF-CONTAINED: imports React and nothing else.
 */

export interface Pillar {
  /** The figure in the dashed marker — "01". */
  number: string;
  category: string;
  title: string;
  description: string;
  /** One of the built-in grounds, or leave it and set `gradient`. */
  tone?: "environment" | "social" | "governance";
  /** A full CSS background value, when the built-in grounds don't suit. */
  gradient?: string;
  /** Optional looping video behind the panel. Muted and decorative. */
  video?: string;
}

export interface PillarPanelsProps {
  /** Names the section for assistive technology and the document outline. */
  heading?: string;
  pillars?: Pillar[];
}

const DEFAULTS = {
  heading: "ESG in practice",
  pillars: [
    {
      number: "01",
      category: "Environmental",
      title: "Water without extraction.",
      description:
        "Generating water from atmospheric moisture can reduce dependence on groundwater, tankers, and single-use bottled-water supply chains.",
      tone: "environment" as const,
    },
    {
      number: "02",
      category: "Social",
      title: "Access where it matters.",
      description:
        "Decentralised systems are designed to bring dependable water closer to homes, workplaces, institutions, and communities.",
      tone: "social" as const,
    },
    {
      number: "03",
      category: "Governance",
      title: "Performance you can verify.",
      description:
        "Connected monitoring, defined quality processes, and measurable output support responsible operation and transparent decision-making.",
      tone: "governance" as const,
    },
  ] as Pillar[],
};

const STYLES = `
[data-block="PillarPanels"],
[data-block="PillarPanels"] :is(h2, h3, p, div) {
  font-family: "Google Sans Flex", "Inter Variable", Inter, ui-sans-serif, system-ui, -apple-system, sans-serif;
}
[data-block="PillarPanels"] {
  position: relative;
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  min-height: 90svh;
  overflow: hidden;
  isolation: isolate;
  color: #fff;
  background: #10292b;
}

/* Named so the document has an outline; the panels are what is read. */
[data-block="PillarPanels"] .pp-sr {
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: -1px;
  overflow: hidden;
  clip: rect(0, 0, 0, 0);
  white-space: nowrap;
  border: 0;
}

[data-block="PillarPanels"] .pp-panel {
  position: relative;
  display: flex;
  align-items: center;
  min-width: 0;
  min-height: 90svh;
  padding: clamp(110px, 15vh, 160px) clamp(30px, 3.7vw, 72px) 72px;
  overflow: hidden;
  isolation: isolate;
}
[data-block="PillarPanels"] .pp-panel + .pp-panel {
  border-left: 1px solid rgb(255 255 255 / 18%);
}
/* Ground, then video, then a scrim: three layers under the words, so the text
   keeps its contrast whatever the media does. */
[data-block="PillarPanels"] .pp-panel::before {
  content: "";
  position: absolute;
  z-index: -3;
  inset: 0;
  background: var(--pp-ground);
}
[data-block="PillarPanels"] .pp-panel::after {
  content: "";
  position: absolute;
  z-index: -1;
  inset: 0;
  background: linear-gradient(180deg, rgb(3 12 14 / 8%), rgb(3 12 14 / 42%));
}
[data-block="PillarPanels"] .pp-panel[data-video="true"]::after {
  background: linear-gradient(180deg, rgb(3 12 14 / 40%), rgb(3 12 14 / 58%));
}
[data-block="PillarPanels"] .pp-video {
  position: absolute;
  z-index: -2;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
}

[data-block="PillarPanels"] .pp-content {
  width: 100%;
  max-width: 510px;
}
[data-block="PillarPanels"] .pp-marker {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 52px;
  height: 52px;
  border: 1px dashed rgb(255 255 255 / 66%);
  border-radius: 50%;
  font-size: 14px;
  font-weight: 600;
  line-height: 1;
  letter-spacing: 0.04em;
  font-variant-numeric: tabular-nums;
}
[data-block="PillarPanels"] .pp-category {
  margin: 34px 0 0;
  color: rgb(255 255 255 / 62%);
  font-size: 12px;
  font-weight: 600;
  line-height: 1;
  letter-spacing: 0.05em;
  text-transform: uppercase;
}
[data-block="PillarPanels"] .pp-title {
  max-width: 12ch;
  margin: 14px 0 0;
  font-size: clamp(28px, 2vw, 38px);
  font-weight: 400;
  line-height: 1.02;
  letter-spacing: -0.04em;
}
[data-block="PillarPanels"] .pp-description {
  max-width: 34ch;
  margin: 32px 0 0;
  color: rgb(255 255 255 / 84%);
  font-size: clamp(15px, 1vw, 18px);
  font-weight: 300;
  line-height: 1.4;
  letter-spacing: -0.02em;
}

@media (max-width: 1023px) {
  [data-block="PillarPanels"] { display: block; }
  [data-block="PillarPanels"] .pp-panel {
    min-height: 64.8svh;
    padding: 110px 32px 64px;
  }
  [data-block="PillarPanels"] .pp-panel + .pp-panel {
    border-top: 1px solid rgb(255 255 255 / 18%);
    border-left: 0;
  }
  [data-block="PillarPanels"] .pp-content { max-width: 640px; }
  [data-block="PillarPanels"] .pp-title { font-size: clamp(32px, 5vw, 42px); }
}

@media (max-width: 767px) {
  [data-block="PillarPanels"] .pp-panel { min-height: 612px; padding: 112px 20px 54px; }
  [data-block="PillarPanels"] .pp-marker { width: 48px; height: 48px; }
  [data-block="PillarPanels"] .pp-category { margin-top: 30px; }
  [data-block="PillarPanels"] .pp-title { font-size: 34px; }
  [data-block="PillarPanels"] .pp-description { margin-top: 28px; font-size: 16px; }
}

/* A looping video is motion nobody asked for; the ground was designed to carry
   the panel on its own. */
@media (prefers-reduced-motion: reduce) {
  [data-block="PillarPanels"] .pp-video { display: none; }
}
`;

/** The built-in grounds, so a panel reads as its own place without any media. */
const GROUNDS: Record<string, string> = {
  environment:
    "radial-gradient(circle at 20% 20%, rgb(83 165 166 / 34%), transparent 42%), linear-gradient(145deg, #184f54 0%, #102f32 58%, #0a2225 100%)",
  social:
    "radial-gradient(circle at 76% 14%, rgb(97 162 184 / 28%), transparent 40%), linear-gradient(145deg, #274f60 0%, #1a3742 56%, #10272f 100%)",
  governance:
    "radial-gradient(circle at 30% 16%, rgb(132 165 145 / 24%), transparent 38%), linear-gradient(145deg, #3a5149 0%, #273a35 55%, #172824 100%)",
};

export function PillarPanels(props: PillarPanelsProps) {
  const p = { ...DEFAULTS, ...props };
  const pillars = p.pillars.length ? p.pillars : DEFAULTS.pillars;

  return (
    <section data-block="PillarPanels" aria-label={p.heading}>
      <style dangerouslySetInnerHTML={{ __html: STYLES }} />
      <h2 className="pp-sr">{p.heading}</h2>

      {pillars.map((pillar) => (
        <article
          key={pillar.category}
          className="pp-panel"
          data-video={Boolean(pillar.video)}
          style={{ ["--pp-ground" as string]: pillar.gradient ?? GROUNDS[pillar.tone ?? "environment"] }}
        >
          {pillar.video && (
            <video className="pp-video" autoPlay muted loop playsInline preload="metadata" aria-hidden="true">
              <source src={pillar.video} type="video/mp4" />
            </video>
          )}
          <div className="pp-content">
            <div className="pp-marker">{pillar.number}</div>
            <p className="pp-category">{pillar.category}</p>
            <h3 className="pp-title">{pillar.title}</h3>
            <p className="pp-description">{pillar.description}</p>
          </div>
        </article>
      ))}
    </section>
  );
}

/* A named export as well as the default, so a project that takes this file can
   import it either way. */
export default PillarPanels;