Not sure what's wrong with it?
Send us a photo. A person on the bench will reply within a day.
An inset card with copy beside the actions and an optional square image.
ConversionSection1 dependency Original, MIT
Send us a photo. A person on the bench will reply within a day.
Using Claude Code? Give it the agent prompt and skip these steps.
foundation.css holds the tokens every component reads.
---
// src/layouts/Layout.astro
import "../styles/foundation.css";
--- ---
import CtaCard from "../components/viewership/cta-card/CtaCard.astro";
---
<CtaCard /> | Prop | Type | Default | Description |
|---|---|---|---|
title | string | · | Headline. |
accent | string | · | Closing words of the headline. |
body | string | · | Supporting line. |
primary | { label: string; href: string } | · | Main action. |
secondary | { label: string; href: string } | · | Text-style second action. |
as | "h2" | "h3" | "h2" | Heading element. |
slot: media | slot | · | Optional square image at the start of the card. |
---
/**
* CtaCard: an inset card with copy on one side and the actions on the other,
* plus an optional image strip. Sits on the page ground.
*
* @example
* <CtaCard title="Not sure what's wrong with it?" primary={{ label: "Send a photo", href: "/help" }}>
* <img slot="media" src="/sun.webp" alt="" />
* </CtaCard>
*/
import Button from "../button/Button.astro";
interface Cta {
label: string;
href: string;
}
interface Props {
title: string;
accent?: string;
body?: string;
primary: Cta;
secondary?: Cta;
as?: "h2" | "h3";
class?: string;
}
const { title, accent, body, primary, secondary, as: Heading = "h2", class: className } = Astro.props;
const hasMedia = Astro.slots.has("media");
---
<section class:list={["vs-cta-card", { "has-media": hasMedia }, className]}>
<div class="vs-cta-card__inner">
<div class="vs-cta-card__panel">
{
hasMedia && (
<div class="vs-cta-card__media">
<slot name="media" />
</div>
)
}
<div class="vs-cta-card__copy">
<Heading class="vs-display vs-cta-card__title">{title}{accent && <> <em>{accent}</em></>}</Heading>
{body && <p class="vs-cta-card__body">{body}</p>}
</div>
<div class="vs-cta-card__actions">
<Button href={primary.href} variant="primary">{primary.label}</Button>
{secondary && <Button href={secondary.href} variant="quiet">{secondary.label}</Button>}
</div>
</div>
</div>
</section>
<style>
.vs-cta-card {
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-cta-card__inner {
max-width: var(--vs-container);
margin-inline: auto;
padding: var(--vs-section-y) var(--vs-gutter);
}
.vs-cta-card__panel {
display: grid;
gap: 24px;
align-items: center;
overflow: hidden;
padding: clamp(24px, 5cqi, 56px);
border: var(--vs-border-w) solid var(--vs-line);
border-radius: var(--vs-radius-window);
background: var(--vs-surface);
box-shadow: var(--vs-pop-shadow, var(--vs-shadow-md));
}
.vs-cta-card__media {
width: 96px;
aspect-ratio: 1;
overflow: hidden;
border-radius: var(--vs-radius-card);
}
.vs-cta-card__media :global(img) {
display: block;
width: 100%;
height: 100%;
object-fit: cover;
}
.vs-cta-card__title {
font-size: clamp(1.75rem, 4.5cqi, 3rem);
}
.vs-cta-card__body {
max-width: 30rem;
margin: 0.75rem 0 0;
font-size: 1rem;
line-height: 1.55;
color: var(--vs-ink-2);
}
.vs-cta-card__actions {
display: flex;
flex-wrap: wrap;
gap: 8px;
}
@container (min-width: 760px) {
.vs-cta-card__panel {
grid-template-columns: minmax(0, 1fr) auto;
gap: 40px;
}
.has-media .vs-cta-card__panel {
grid-template-columns: auto minmax(0, 1fr) auto;
}
.vs-cta-card__media {
width: 120px;
}
}
</style> Original, MIT.