# Feature Alternating

> Image and copy rows that swap sides as you scroll, for features that each deserve a picture.

- **Slug:** `feature-alternating`
- **Kind:** section · **Category:** Features
- **Source:** original to Viewership UI
- **Docs page:** https://viewership.ai/ui/components/feature-alternating
- **All files in one bundle:** https://viewership.ai/ui/components/feature-alternating.bundle.txt
- **Structured JSON:** https://viewership.ai/ui/components/feature-alternating.json

## Install

1. Import the foundation tokens once (download: https://viewership.ai/ui/raw/foundation.css.txt):

```astro
---
// src/layouts/Layout.astro
import "../styles/foundation.css";
---
```

2. Copy this file into `src/components/viewership/feature-alternating/`:

   - `FeatureAlternating.astro` (raw: https://viewership.ai/ui/raw/feature-alternating/FeatureAlternating.astro.txt)

npm packages: none.

## Usage

```astro
---
import FeatureAlternating from "../components/viewership/feature-alternating/FeatureAlternating.astro";
---

<FeatureAlternating />
```

## Example

The gallery demo, verbatim. Use the import path from Usage instead of `./`.

```astro
---
import FeatureAlternating from "./FeatureAlternating.astro";
---

<FeatureAlternating
  items={[
    {
      kicker: "Made",
      title: "Mapped from the mill to your pack.",
      body: "Every product page lists where the fabric was woven, cut and sewn, with the batch number printed inside the lid.",
      image: { src: "/ui/demo/contours.webp", alt: "Topographic contour map", width: 1600, height: 1100 },
      link: { label: "See our mills", href: "#" },
    },
    {
      kicker: "Tested",
      title: "Carried for a season before it ships.",
      body: "Guides take each prototype out from first thaw to first snow, then tell us what broke.",
      image: { src: "/ui/demo/halftone-sun.webp", alt: "Halftone sun over the horizon", width: 1200, height: 1200 },
      link: { label: "Meet the crew", href: "#" },
    },
  ]}
/>
```

## Props

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `title` | `string` |  | Optional section headline. Without it, row titles use `as`. |
| `accent` | `string` |  | Closing words of the headline. |
| `items` | `{ title: string; body: string; image: { src; alt; width?; height? }; kicker?: string; link?: { label; href } }[]` |  | Rows, in order. Even rows put the image on the right. |
| `as` | `"h2" \| "h3"` | `"h2"` | Heading element. |

## Source

### `src/components/viewership/feature-alternating/FeatureAlternating.astro`

```astro
---
/**
 * FeatureAlternating: media and copy in rows that swap sides, for features
 * that each deserve a picture. Rows stack on narrow widths.
 *
 * @example
 * <FeatureAlternating items={[
 *   { kicker: "01", title: "Mapped from the mill", body: "...",
 *     image: { src: "/map.webp", alt: "Contour map" }, link: { label: "See the mills", href: "/mills" } },
 * ]} />
 */
interface Picture {
  src: string;
  alt: string;
  width?: number;
  height?: number;
}
interface Link {
  label: string;
  href: string;
}
interface Item {
  title: string;
  body: string;
  image: Picture;
  /** Short label above the row title, e.g. a step number. */
  kicker?: string;
  link?: Link;
}
interface Props {
  title?: string;
  accent?: string;
  items: Item[];
  as?: "h2" | "h3";
  class?: string;
}

const { title, accent, items, as: Heading = "h2", class: className } = Astro.props;
const RowHeading = title ? "h3" : Heading;
---

<section class:list={["vs-feature-alt", className]}>
  <div class="vs-feature-alt__inner">
    {title && <Heading class="vs-display vs-feature-alt__title">{title}{accent && <> <em>{accent}</em></>}</Heading>}
    <div class="vs-feature-alt__rows">
      {
        items.map((item) => (
          <article class="vs-feature-alt__row">
            <div class="vs-feature-alt__media">
              <img src={item.image.src} alt={item.image.alt} width={item.image.width} height={item.image.height} loading="lazy" />
            </div>
            <div class="vs-feature-alt__copy">
              {item.kicker && <p class="vs-feature-alt__kicker">{item.kicker}</p>}
              <RowHeading class="vs-display vs-feature-alt__name">{item.title}</RowHeading>
              <p class="vs-feature-alt__body">{item.body}</p>
              {item.link && (
                <a class="vs-feature-alt__link" href={item.link.href}>
                  {item.link.label}
                </a>
              )}
            </div>
          </article>
        ))
      }
    </div>
  </div>
</section>

<style>
  .vs-feature-alt {
    container-type: inline-size;
    background: var(--vs-page);
    color: var(--vs-ink);
    font-family: var(--vs-font-sans);
    font-stretch: var(--vs-font-stretch);
  }
  .vs-feature-alt__inner {
    max-width: var(--vs-container);
    margin-inline: auto;
    padding: var(--vs-section-y) var(--vs-gutter);
  }
  .vs-feature-alt__title {
    margin-bottom: clamp(32px, 6cqi, 72px);
    font-size: clamp(2rem, 6.5cqi, 4.25rem);
  }
  .vs-feature-alt__rows {
    display: grid;
    gap: clamp(48px, 9cqi, 112px);
  }
  .vs-feature-alt__row {
    display: grid;
    gap: clamp(24px, 5cqi, 64px);
    align-items: center;
  }
  .vs-feature-alt__media {
    aspect-ratio: 5 / 4;
    overflow: hidden;
    border: var(--vs-border-w) solid var(--vs-line);
    border-radius: var(--vs-radius-card);
    background: var(--vs-canvas);
    box-shadow: var(--vs-pop-shadow, none);
  }
  .vs-feature-alt__media img {
    display: block;
    width: 100%;
    height: 100%;
    object-fit: cover;
  }
  .vs-feature-alt__kicker {
    margin: 0 0 14px;
    font-size: 0.8125rem;
    font-weight: 600;
    color: var(--vs-accent-ink);
  }
  .vs-feature-alt__name {
    font-size: clamp(1.75rem, 4.5cqi, 3rem);
  }
  .vs-feature-alt__body {
    max-width: 28rem;
    margin: 1rem 0 0;
    font-size: 1rem;
    line-height: 1.6;
    color: var(--vs-ink-2);
  }
  .vs-feature-alt__link {
    display: inline-block;
    margin-top: 1.5rem;
    padding-bottom: 2px;
    border-bottom: calc(var(--vs-border-w) + 1px) solid var(--vs-accent);
    font-size: 0.9375rem;
    font-weight: 600;
    color: var(--vs-ink);
    text-decoration: none;
  }
  .vs-feature-alt__link:focus-visible {
    outline: none;
    box-shadow: var(--vs-focus-ring);
  }
  @container (min-width: 760px) {
    .vs-feature-alt__row {
      grid-template-columns: minmax(0, 7fr) minmax(0, 5fr);
    }
    .vs-feature-alt__row:nth-child(even) .vs-feature-alt__media {
      order: 2;
    }
    .vs-feature-alt__row:nth-child(even) {
      grid-template-columns: minmax(0, 5fr) minmax(0, 7fr);
    }
  }
</style>
```
