CTA, offer
Install
npx shadcn add @klarhimmel/cta-offerUsage
import { Theme } from '@/components/klarhimmel/theme/style';
import { BlockSurface } from '@/components/klarhimmel/blocks/surface';
import CtaOffer from '@/components/klarhimmel/blocks/cta-offer';
export default function Section() {
return (
<Theme theme="brand">
<BlockSurface>
<CtaOffer props={{
"eyebrow": "Free this week",
"title": "A free review of your website",
"description": "We read your site as a visitor would and tell you what we would change, in writing.",
"points": [
"A page-by-page note on what works and what does not",
"A plan for the pages we would rebuild, as a picture you can click through",
"An honest answer on whether we are the right people"
],
"pointsTitle": "What you get",
"turnaround": "Within 24 hours",
"turnaroundLabel": "Turnaround",
"limit": "Five reviews this week",
"limitLabel": "This week",
"mode": "form",
"link": {
"label": "Ask for a review",
"href": "#review"
},
"form": {
"fieldLabel": "Your site's address",
"fieldType": "domain",
"submit": "Ask for a free review",
"note": "We only use the address to read your site and reply to you."
},
"messages": {
"sent": "Thank you. We have your address and will send the review within 24 hours.",
"error": "Something went wrong. Please try again, or email us."
},
"errors": {
"required": "Fill in this field.",
"email": "Enter an email address like name@company.example.",
"url": "Enter an address that starts with https://, like https://example.com.",
"domain": "Enter a site address like yourcompany.se."
}
}} />
</BlockSurface>
</Theme>
);
}Install
Import
import Block from '@/components/klarhimmel/blocks/cta-offer';- cta/offer
- About
- The offer of the week as one block: what you get in three to five points, how fast (text), this week's limit (text), and one primary action that is either a link or a one-field form.
- Category
- One clear next step near the end of a page.
Notes
When to use it
This week's offer as one block: three to five points of what you get, a turnaround, a limit, and either a link button or a one-field form. For a general closing call without an offer, `cta-card` is simpler.
Writing for it
- Points need three to five entries of up to 120 characters each; write what the person receives, as nouns, not what you do. - Turnaround (60) and limit (60) are plain text with their own labels. Write 'Within 24 hours' and 'Five reviews this week', and change the text when the offer changes. - In form mode, fieldType decides keyboard and validation: domain accepts yourcompany.se or a full address, while url insists on a scheme. Pick the one your owners will really type.
Watch out for
Both the form and the link button are always in the markup; mode only hides the other one, and both stay visible in the editor. The sent and error messages appear only when a host sets status, so the block alone never shows a success state.
Pairs well with
Usage
The block in a page, with its default content (long lists shortened). Every prop is in the table below.
import { Theme } from '@/components/klarhimmel/theme/style';
import { BlockSurface } from '@/components/klarhimmel/blocks/surface';
import CtaOffer from '@/components/klarhimmel/blocks/cta-offer';
export default function Section() {
return (
<Theme theme="brand">
<BlockSurface>
<CtaOffer props={{
"eyebrow": "Free this week",
"title": "A free review of your website",
"description": "We read your site as a visitor would and tell you what we would change, in writing.",
"pointsTitle": "What you get",
"points": [
"A page-by-page note on what works and what does not",
"A plan for the pages we would rebuild, as a picture you can click through"
],
"turnaroundLabel": "Turnaround",
"turnaround": "Within 24 hours",
"limitLabel": "This week",
"limit": "Five reviews this week",
"mode": "form",
"link": {
"label": "Ask for a review",
"href": "#review"
},
"errors": {
"required": "Fill in this field.",
"email": "Enter an email address like name@company.example.",
"url": "Enter an address that starts with https://, like https://example.com.",
"domain": "Enter a site address like yourcompany.se."
},
"messages": {
"sent": "Thank you. We have your address and will send the review within 24 hours.",
"error": "Something went wrong. Please try again, or email us."
},
"form": {
"fieldLabel": "Your site's address",
"fieldType": "domain",
"submit": "Ask for a free review",
"note": "We only use the address to read your site and reply to you."
}
}} />
</BlockSurface>
</Theme>
);
}Props
The type, default and text limit of every prop. On a real page, over-limit text is kept and checkSections flags it (Installation, Check a page before you ship); this live preview shows the default until it fits. The panel above edits all props.
| Prop | Type | Default | Description |
|---|---|---|---|
eyebrow | string | 'Free this week' | Text, up to 60 characters. Optional; empty is not rendered. |
title | string | 'A free review of your website' | Text, up to 120 characters. |
description | string | 'We read your site as a visitor woul…' | Text, up to 300 characters. Optional; empty is not rendered. |
points | string[] | 3 items | List of 3 to 5. |
pointsTitle | string | 'What you get' | Text, up to 60 characters. |
turnaround | string | 'Within 24 hours' | Text, up to 60 characters. |
turnaroundLabel | string | 'Turnaround' | Text, up to 40 characters. |
limit | string | 'Five reviews this week' | Text, up to 60 characters. |
limitLabel | string | 'This week' | Text, up to 40 characters. |
mode | 'link' | 'form' | 'form' | One of 'link', 'form'. |
link | Link | {…} | See the type. |
form | { fieldLabel, fieldType, fieldName, submit, note } | {…} | Fields: fieldLabel, fieldType ('domain' | 'url' | 'email' | 'text'), fieldName (optional), submit, note (optional). |
action | string | - | Text, up to 300 characters. Optional; empty is not rendered. |
method | 'post' | 'get' | - | One of 'post', 'get'. Optional; empty is not rendered. |
hidden | { } | - | Optional; empty is not rendered. |
captcha | boolean | - | Optional; empty is not rendered. |
captchaSitekey | string | - | Text, up to 100 characters. Optional; empty is not rendered. |
status | 'idle' | 'sent' | 'error' | - | One of 'idle', 'sent', 'error'. Optional; empty is not rendered. |
messages | { sent, error } | {…} | Fields: sent, error. Optional; empty is not rendered. |
errors | { required, email, url, domain } | {…} | Fields: required (optional), email (optional), url (optional), domain (optional). Optional; empty is not rendered. |
headingLevel | '1' | '2' | - | One of '1', '2'. Optional; empty is not rendered. |