Prosepopular
apps/www/src/sections/Prose.astro · 120 lines · group Text & prose
Prose — a text section: kicker, title and lead, then markdown in a reading column. The section for anything that is words rather than a grid. Use for: mission, about us, a certification text, terms, a long explanation, a page that is mostly paragraphs. Not for: a first screen (<HeroText>); a list of parallel points (<FeatureList>); questions and answers (<Faq>). Children: markdown between the tags — paragraphs, headings, lists, links, emphasis, tables, code. Also the sub-block tags <ProseItem> (a titled sub-section) and <ProseAside> (a card floated beside the text). Layout: align="center" (default) centers the heading and the text; align="left" is the shape for long reading. `width="text"` keeps a comfortable reading measure, `width="wide"` uses the whole print zone — use "wide" together with a <ProseAside>. `heading` aligns the heading independently (a centered heading over a left-aligned body is the certification-page shape). Picture: `image` (file name in images/) is shown under the text — a certificate scan, a schema. Links: `linkText` + `href` render an arrow link; primary/secondary buttons follow under the text.
In the fleet
Used on 3 sites built with this engine.
Samples 2
WHY WE BUILT IT
A thermometer is not a decision
Growers used to choose between a thermometer that told them nothing and a research mast nobody could afford.
Nordvind sits in between: the sensors that change a decision, and none of the ones that only look good on a datasheet.
- Frost, wind, rain and leaf wetness — inside the crop
- One battery per season, one bolt per station
- Readings exported as CSV, PDF or through the API
MDX of this sample
<Prose kicker="WHY WE BUILT IT" title="A thermometer is not a decision">
Growers used to choose between a thermometer that told them nothing and a research mast nobody could afford.
Nordvind sits in between: the sensors that change a decision, and none of the ones that only look good on a datasheet.
* Frost, wind, rain and leaf wetness — inside the crop
* One battery per season, one bolt per station
* Readings exported as CSV, PDF or through the API
</Prose>What we promise, and what we do not
We promise that a station keeps measuring when the network is down, that a sensor can be replaced by the person who found the problem, and that the data leaves with you if you leave us.
We do not promise a forecast that is right every time. Nobody can. What we can do is tell you what is happening in your field right now, minutes after it starts happening.
MDX of this sample
<Prose width="wide" title="What we promise, and what we do not" heading="left" decor="dots">
We promise that a station keeps measuring when the network is down, that a sensor can be replaced by the person who found the problem, and that the data leaves with you if you leave us.
We do not promise a forecast that is right every time. Nobody can. What we can do is tell you what is happening in your field right now, minutes after it starts happening.
<ProseAside>
<Quote quote="Measure where the crop is, alert the person on duty, keep the history." name="Anna Lindqvist" role="Head of agronomy, Almara Farms" avatar="avatar-01.jpg" />
</ProseAside>
</Prose>Props
| Name | Type | Required | Default | Description |
|---|---|---|---|---|
| id | string | anchor of the section | ||
| kicker | string | short uppercase line above the title, painted with the brand gradient | ||
| title | string | the section heading | ||
| description | string | the lead paragraph, larger than the body | ||
| align | 'center' | 'left' | 'center' | alignment of heading and text | |
| width | 'text' | 'wide' | 'text' | 'text' keeps the reading measure; 'wide' uses the whole print zone — what a section with a <ProseAside> wants | |
| heading | 'center' | 'left' | header alignment; defaults to `align` | ||
| theme | 'light' | 'muted' | 'dark' | 'brand' | 'brand-dark' | 'light' | background of the section | |
| decor | 'none' | 'dots' | 'dots-left' | 'none' | decorative dot grid behind the content | |
| image | string | file name in images/, shown under the text | ||
| linkText | string | text of an arrow link under the text; shown only with href | ||
| href | string | target of that link | ||
| primaryText | string | text of the solid brand button; omit to hide the button | ||
| primaryHref | string | '#' | link of the primary button | |
| secondaryText | string | text of the second, outlined button | ||
| secondaryHref | string | '#' | link of the secondary button | |
| class | string | escape hatch for compositions: extra classes for the section shell |
Source apps/www/src/sections/Prose.astro @ gitt.one
---
/**
* Prose — a text section: kicker, title and lead, then markdown in a reading
* column. The section for anything that is words rather than a grid.
*
* Use for: mission, about us, a certification text, terms, a long explanation,
* a page that is mostly paragraphs.
* Not for: a first screen (<HeroText>); a list of parallel points
* (<FeatureList>); questions and answers (<Faq>).
* Children: markdown between the tags — paragraphs, headings, lists, links,
* emphasis, tables, code. Also the sub-block tags <ProseItem> (a titled
* sub-section) and <ProseAside> (a card floated beside the text).
* Layout: align="center" (default) centers the heading and the text;
* align="left" is the shape for long reading. `width="text"` keeps a
* comfortable reading measure, `width="wide"` uses the whole print zone —
* use "wide" together with a <ProseAside>. `heading` aligns the heading
* independently (a centered heading over a left-aligned body is the
* certification-page shape).
* Picture: `image` (file name in images/) is shown under the text — a
* certificate scan, a schema.
* Links: `linkText` + `href` render an arrow link; primary/secondary buttons
* follow under the text.
*/
import { Image } from 'astro:assets';
import { resolveImage } from '../lib/images';
import Section from './_Section.astro';
import type { Theme } from './_Section.astro';
import Button from './_Button.astro';
interface Props {
/** anchor of the section */
id?: string;
/** short uppercase line above the title, painted with the brand gradient */
kicker?: string;
/** the section heading */
title?: string;
/** the lead paragraph, larger than the body */
description?: string;
/** alignment of heading and text */
align?: 'center' | 'left';
/** 'text' keeps the reading measure; 'wide' uses the whole print zone — what a section with a <ProseAside> wants */
width?: 'text' | 'wide';
/** header alignment; defaults to `align` */
heading?: 'center' | 'left';
/** background of the section */
theme?: Theme;
/** decorative dot grid behind the content */
decor?: 'none' | 'dots' | 'dots-left';
/** file name in images/, shown under the text */
image?: string;
/** text of an arrow link under the text; shown only with href */
linkText?: string;
/** target of that link */
href?: string;
/** text of the solid brand button; omit to hide the button */
primaryText?: string;
/** link of the primary button */
primaryHref?: string;
/** text of the second, outlined button */
secondaryText?: string;
/** link of the secondary button */
secondaryHref?: string;
/** escape hatch for compositions: extra classes for the section shell */
class?: string;
}
const {
id, kicker, title, description, align = 'center', heading, width = 'text', theme = 'light', decor = 'none', image,
linkText, href, primaryText, primaryHref = '#', secondaryText, secondaryHref = '#', class: extra = '',
} = Astro.props;
const img = image ? await resolveImage(image) : undefined;
const hasAside = Astro.slots.has('aside');
const center = align === 'center' && !hasAside;
const headerCenter = (heading ?? align) === 'center' && !hasAside;
// with an aside the text column is narrow, so a centered heading belongs
// above the whole grid (the product-quality shape)
const headerAbove = hasAside && (heading ?? align) === 'center' && Boolean(title || kicker || description);
---
<Section id={id} theme={theme} class={extra} decor={decor}>
{headerAbove && (
<div class="mx-auto mb-12 max-w-3xl text-center">
{kicker && (
<p class="text-sm font-semibold uppercase tracking-wide">
<span class="bg-gradient-to-r from-primary-500 to-primary-alt-600 bg-clip-text text-transparent">{kicker}</span>
</p>
)}
{title && <h2 class="mt-3 text-3xl font-extrabold tracking-tight sm:text-4xl">{title}</h2>}
{description && <p class="mt-6 text-xl leading-relaxed text-gray-500 [.bg-stone-800_&]:text-primary-100">{description}</p>}
</div>
)}
<div class:list={[hasAside && 'lg:grid lg:grid-cols-2 lg:gap-12 lg:items-start']}>
<div class:list={[width === 'wide' ? 'max-w-none' : 'max-w-3xl', (center || headerCenter) && 'mx-auto', center && 'text-center']}>
{!headerAbove && (
<div class:list={[headerCenter && !center && 'text-center']}>
{kicker && (
<p class="text-sm font-semibold uppercase tracking-wide">
<span class="bg-gradient-to-r from-primary-500 to-primary-alt-600 bg-clip-text text-transparent">{kicker}</span>
</p>
)}
{title && <h2 class="mt-3 text-3xl font-extrabold tracking-tight sm:text-4xl">{title}</h2>}
{description && <p class="mt-6 text-xl leading-relaxed text-gray-500 [.bg-stone-800_&]:text-primary-100">{description}</p>}
</div>
)}
<div class:list={['prose prose-gray mt-6 max-w-none text-gray-500 after:block after:clear-both after:content-[""] [&_h2_a]:no-underline [&_h3_a]:no-underline [&_h2]:mt-0 [&_h3]:mt-0', center && 'mx-auto']}>
<slot />
</div>
{linkText && href && <p class="mt-6"><Button href={href} kind="link">{linkText}</Button></p>}
{(primaryText || secondaryText) && (
<div class:list={['mt-8 flex flex-wrap gap-4', center && 'justify-center']}>
{primaryText && <Button href={primaryHref}>{primaryText}</Button>}
{secondaryText && <Button href={secondaryHref} kind="secondary">{secondaryText}</Button>}
</div>
)}
{img && <Image src={img} alt="" widths={[400, 840]} class:list={['mt-8 w-full max-w-2xl rounded-lg shadow-lg', center && 'mx-auto']} />}
</div>
{hasAside && <div class="mt-12 lg:mt-0"><slot name="aside" /></div>}
</div>
</Section>Source links point to engine-astro@7b686388. Samples are demo content: the components are the real ones, the copy and the pictures are made up.