Skip to content
Download ZIP

Emerald accent palette · 5.7 KB ZIP File receipt View as Markdown View code

Preview

Fit to the available width. The frame follows the height of its content; previews taller than the maximum auto-height scroll inside it.

Give this component to your coding agent Copy a prompt that fetches this exact version and palette through the PageSugar MCP server.
cmp_hero_text_first_01 · version 1.0.0 · Emerald accent palette
Using the PageSugar MCP server, fetch component cmp_hero_text_first_01 version 1.0.0 with variant "emerald", first inspect its requirements and license status and confirm this project uses Svelte 5 and Tailwind CSS 4. Retrieve every manifest file, including binary assets and any manifest-only response files, preserving relative paths. Then integrate the source and follow its usage notes. Run project checks, review the browser result and report anything unverified. Do not substitute another version or invent missing files.

Not connected yet? Set up the MCP server

Code

Palette
Emerald accent
Version
1.0.0
Digest
Full digest
sha256-b1b1db42624bf9ffad514ef42a5fe686d1547eca47984b73211456b22950d636
TextFirstHero.svelte Svelte · 7.6 KB Raw
<script module lang="ts">
	export interface HeroAction {
		/** Link text. Say where it goes: "Start planning free", not "Click here". */
		label: string;
		href: string;
	}
</script>

<script lang="ts">
	import type { Snippet } from 'svelte';

	interface Props {
		/** The headline. Two short lines read best; three still hold. */
		title: string;
		/** Supporting paragraph under the headline, held to a readable measure. */
		description?: string;
		/** Short line above the headline, such as a product category or a place. */
		eyebrow?: string;
		/** The main call to action, rendered as a filled link. */
		primaryAction: HeroAction;
		/** An optional lower-emphasis link beside the primary one. */
		secondaryAction?: HeroAction;
		/** Alignment of the whole block. Centre only short copy. */
		align?: 'start' | 'center';
		/** 1 when the hero opens the page; 2 when the page already has an h1. */
		headingLevel?: 1 | 2;
		/** Optional note under the actions, such as pricing or platform support. */
		footer?: Snippet;
	}

	let {
		title,
		description,
		eyebrow,
		primaryAction,
		secondaryAction,
		align = 'start',
		headingLevel = 1,
		footer
	}: Props = $props();

	/*
	 * A headline longer than this sets one step smaller on a wider measure, so a long sentence
	 * reads as three or four lines of display type instead of a five-line slab. Characters, not
	 * words, so it holds for scripts without spaces.
	 */
	const LONG_TITLE = 60;

	const uid = $props.id();
	const heading = $derived(`h${headingLevel}`);
	const centered = $derived(align === 'center');
	const long = $derived([...title.trim()].length > LONG_TITLE);
</script>

<section
	class="hero-text-first px-4 py-20 sm:px-6 sm:py-24 lg:px-8 lg:py-32"
	aria-labelledby="{uid}-title"
>
	<div class={['mx-auto max-w-6xl', centered ? 'text-center' : 'text-start']}>
		{#if eyebrow}
			<p
				class="tracked text-xs/4 font-semibold tracking-[0.06em] text-balance break-words text-[var(--_eyebrow)] uppercase"
			>
				{eyebrow}
			</p>
		{/if}

		<!-- The measure is in em so the headline keeps the same shape at every size: about two
		     short lines at 72 px, however wide the container. -->
		<svelte:element
			this={heading}
			id="{uid}-title"
			class={[
				'title tight font-semibold tracking-[-0.02em] text-balance break-words text-[var(--_ink)] sm:tracking-[-0.03em]',
				long
					? 'max-w-[18em] text-3xl/[1.15] sm:text-5xl/[1.1] lg:text-6xl/[1.05]'
					: 'max-w-[12em] text-4xl/[1.1] sm:text-6xl/[1.08] lg:text-7xl/[1.05]',
				centered && 'mx-auto',
				eyebrow && 'mt-2'
			]}
		>
			{title}
		</svelte:element>

		{#if description}
			<p
				class={[
					'lead mt-4 max-w-xl text-lg/7 text-pretty break-words text-[var(--_muted)] sm:mt-6 sm:text-xl/8',
					centered && 'mx-auto'
				]}
			>
				{description}
			</p>
		{/if}

		<div
			class={[
				'mt-8 flex flex-col gap-3 sm:mt-10 sm:flex-row sm:flex-wrap sm:items-center',
				centered && 'sm:justify-center'
			]}
		>
			<a
				href={primaryAction.href}
				class="action action-primary inline-flex min-h-12 max-w-full items-start justify-center rounded-lg bg-[var(--_accent)] px-6 py-3 text-center text-base/6 font-medium text-[var(--_on-accent)] focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-[var(--_accent)]"
			>
				<span class="min-w-0 text-balance wrap-anywhere">{primaryAction.label}</span>
			</a>
			{#if secondaryAction}
				<a
					href={secondaryAction.href}
					class="action action-secondary inline-flex min-h-12 max-w-full items-start justify-center rounded-lg px-6 py-3 text-center text-base/6 font-medium text-[var(--_ink)] focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-[var(--_accent)]"
				>
					<!-- The arrow sits inline, joined to the last word, so it trails the label's last line
					     however the label wraps. -->
					<span class="min-w-0 text-balance wrap-anywhere"
						>{secondaryAction.label}&#8288;<svg
							class="arrow ms-2 inline-block size-5"
							viewBox="0 0 20 20"
							fill="none"
							aria-hidden="true"
						>
							<path
								d="M4 10h11.5M11 5.5l4.5 4.5-4.5 4.5"
								stroke="currentColor"
								stroke-width="1.5"
								stroke-linecap="round"
								stroke-linejoin="round"
							/>
						</svg></span
					>
				</a>
			{/if}
		</div>

		{#if footer}
			<div
				class={[
					'note mt-4 max-w-[30em] text-sm/6 text-pretty break-words text-[var(--_muted)] sm:mt-6',
					centered && 'mx-auto'
				]}
			>
				{@render footer()}
			</div>
		{/if}
	</div>
</section>

<style>
	/* Public tokens: set --hero-text-first-* on this section or any ancestor to retone it. */
	.hero-text-first {
		--_accent: var(--hero-text-first-accent, #065f46);
		--_on-accent: var(--hero-text-first-on-accent, #ffffff);
		--_ink: var(--hero-text-first-ink, #18181b);
		--_muted: var(--hero-text-first-muted, #52525b);
		--_hairline: var(--hero-text-first-hairline, rgb(0 0 0 / 0.1));
		--_eyebrow: var(--hero-text-first-eyebrow, #52525b);
	}

	.action {
		transition-property: color, background-color, box-shadow, transform;
		transition-duration: 150ms;
		transition-timing-function: cubic-bezier(0.2, 0, 0, 1);
	}
	.action:active {
		transform: scale(0.98);
		transition-duration: 80ms;
	}
	/* Hover moves the tone one step towards the text colour instead of fading the button. */
	.action-primary:hover {
		background-color: color-mix(in oklab, var(--_accent) 86%, var(--_on-accent));
	}
	/* The ghost link gains a quiet fill and its hairline on hover, so it reads as a control. */
	.action-secondary:hover {
		background-color: color-mix(in oklab, var(--_ink) 4%, transparent);
		box-shadow: inset 0 0 0 1px var(--_hairline);
	}
	.arrow {
		/* Centres the 20 px glyph on the 16 px label's x-height. */
		vertical-align: -0.3125em;
		transition: translate 150ms cubic-bezier(0.2, 0, 0, 1);
	}
	.action-secondary:hover .arrow {
		translate: 2px 0;
	}
	/* The arrow points along the reading direction. */
	.arrow:dir(rtl) {
		scale: -1 1;
	}
	.action-secondary:hover .arrow:dir(rtl) {
		translate: -2px 0;
	}

	/* Arabic and Hebrew are never letter-spaced, and Arabic needs taller lines than Latin display. */
	.hero-text-first :is(.tight, .tracked):dir(rtl) {
		letter-spacing: 0;
	}
	/*
	 * Arabic and Hebrew have no case, so the eyebrow drops its caps treatment for any Latin in it,
	 * and steps up to 14 px: without capitals, 12 px Arabic reads a size smaller than Latin caps.
	 */
	.hero-text-first .tracked:dir(rtl) {
		text-transform: none;
		font-size: 0.875rem;
	}
	.hero-text-first .title:dir(rtl) {
		line-height: 1.3;
	}
	/* Arabic sets more characters to the em than Latin, so the lead and note take a shorter measure. */
	.hero-text-first :is(.lead, .note):dir(rtl) {
		max-width: 23em;
	}
	/*
	 * CJK is set untracked and never breaks mid-word: Japanese breaks at phrase boundaries where
	 * the browser supports it, Korean only at its spaces, Chinese between characters.
	 */
	.hero-text-first :is(.tight, .tracked):is(:lang(ja), :lang(zh), :lang(ko)) {
		letter-spacing: 0;
		line-break: strict;
	}
	.hero-text-first :is(.title, .lead, .note):is(:lang(ja), :lang(zh), :lang(ko)) {
		line-break: strict;
	}
	.hero-text-first :is(.title, .lead, .note):lang(ja) {
		word-break: auto-phrase;
	}
	.hero-text-first :is(.title, .lead, .note):lang(ko) {
		word-break: keep-all;
	}
	.hero-text-first .title:is(:lang(ja), :lang(zh), :lang(ko)) {
		line-height: 1.2;
	}

	@media (prefers-reduced-motion: reduce) {
		.action {
			transition-property: color, background-color, box-shadow;
		}
		.action:active {
			transform: none;
		}
		.arrow,
		.action-secondary:hover .arrow,
		.action-secondary:hover .arrow:dir(rtl) {
			transition: none;
			translate: none;
		}
	}
</style>

Presentational only: renders the headline, paragraph and links you pass, and each action is an ordinary link. No additional runtime packages beyond Svelte and Tailwind CSS, no click tracking and no media. It needs no client JavaScript: the actions are native links and every state is CSS. Customer logos, ratings or counts are out of scope; add them from real data with a separate component.

Suggested location
src/lib/components/hero-text-first-01
Required props
titleprimaryAction

Limitations

  • Renders exactly one primary action; a third link belongs in your header or in the footer snippet.
  • Centre alignment suits a headline of two or three lines and a short paragraph; long copy reads better start-aligned.
  • The section has no background of its own; set one on its parent, and retone the text tokens to match a dark or tinted page.
  • Light appearance by default. The tokens retone it for a dark page, but no dark mode is declared or selected automatically.
  • Japanese text breaks at phrase boundaries only in browsers that support word-break: auto-phrase (Chromium 119 and later); elsewhere it breaks between characters, with strict line-breaking rules.

Example

Svelte
<!-- Illustrative content: replace the copy and links before publishing. -->
<script lang="ts">
	import TextFirstHero from '$lib/components/hero-text-first-01/TextFirstHero.svelte';
</script>

<TextFirstHero
	eyebrow="Roadmaps and planning"
	title="Plan the quarter. Ship the week."
	description="One workspace for the roadmap, the sprint boards and the release timeline."
	primaryAction={{ label: 'Start planning free', href: '/signup' }}
	secondaryAction={{ label: 'Take the product tour', href: '/tour' }}
>
	{#snippet footer()}
		<p>Free for up to three people.</p>
	{/snippet}
</TextFirstHero>

Using the hero#

Copy TextFirstHero.svelte into src/lib/components/hero-text-first-01/. Replace the example copy and links with your own. Both actions are ordinary links; the hero does not track clicks.

HeroAction is exported from the component's module script. Its shape is { label, href }.

Keep the headline to two short lines where you can. The heading is held to max-w-[12em], which sets a headline of about 30 characters as a balanced two-line block at every size. A headline over 60 characters (LONG_TITLE at the top of the script) steps down one size, to 30, 48 and 60 px, on max-w-[18em], so a sentence-long headline reads as three or four lines rather than a slab.

Alignment#

align="start" (the default) puts every element on one left edge, which reads best for most copy. align="center" centres the headline, paragraph, actions and note; use it only when the paragraph is two or three lines.

footer is a snippet rendered under the actions in small muted text:

Svelte
<TextFirstHero
	title="A dentist that runs on time"
	primaryAction={{ label: 'Book an appointment', href: '/book' }}
>
	{#snippet footer()}
		<p>New patients welcome. Open until 8 pm on weekdays.</p>
	{/snippet}
</TextFirstHero>

Links inside the note are yours to style; an underline is right there, because the note is prose.

Retoning#

Variable Default Draws
--hero-text-first-accent #18181b Primary link fill, focus rings
--hero-text-first-on-accent #ffffff Text on the primary link
--hero-text-first-ink #18181b Headline, secondary link
--hero-text-first-muted #52525b Paragraph, footer note
--hero-text-first-eyebrow #52525b Eyebrow
--hero-text-first-hairline rgb(0 0 0 / 0.1) Secondary link ring on hover

A worked retone for a dark page:

Svelte
<div
	class="bg-zinc-950"
	style="
		--hero-text-first-ink: #fafafa;
		--hero-text-first-muted: #a1a1aa;
		--hero-text-first-eyebrow: #a1a1aa;
		--hero-text-first-hairline: rgb(255 255 255 / 0.12);
		--hero-text-first-accent: #fafafa;
		--hero-text-first-on-accent: #18181b;
	"
>
	<TextFirstHero
		title="Plan the quarter. Ship the week."
		primaryAction={{ label: 'Start planning free', href: '/signup' }}
	/>
</div>

Props and content inputs#

On this page
NameTypeRequiredDefaultDescription
titlestringYesNoneThe headline. Two short lines read best; over 60 characters it steps down one size on a wider measure and stays balanced.
primaryActionHeroActionYesNoneThe main call to action, { label, href }, rendered as a filled link. The label should say where it goes.
descriptionstringNoNoneSupporting paragraph under the headline, held to about 60 characters a line.
eyebrowstringNoNoneShort uppercase line above the headline, such as a product category or a place. Rendered before the heading, not inside it.
secondaryActionHeroActionNoNoneOptional lower-emphasis link, { label, href }, with a trailing arrow joined to its last word. Omitted, nothing renders in its place.
align'start' | 'center'No'start'Alignment of the whole block. Centre only short copy.
headingLevel1 | 2No1Heading level of the title. Use 2 when the page already has an h1.

Customization#

On this page

Change content through props and the footer snippet, retone the section through six --hero-text-first-* CSS variables, and edit Tailwind classes in the source for type size, spacing or measure.

  • Accent: set --hero-text-first-accent and --hero-text-first-on-accent together. The accent fills the primary link and draws both focus rings; keep on-accent text at 4.5:1 or better against it. The hover shade mixes 14% of on-accent into the accent, so check the hover shade too: a mid-tone accent that passes at rest can drop under 4.5:1 on hover (Tailwind's emerald-700 does; emerald-800 does not).
  • Text: --hero-text-first-ink sets the headline and the secondary link; --hero-text-first-muted sets the paragraph and the footer note. Keep muted at 4.5:1 against your background. --hero-text-first-hairline is the ring the secondary link shows on hover.
  • Eyebrow: --hero-text-first-eyebrow colours the eyebrow and defaults to the same #52525b as the muted tone; set it separately when you change muted. Keep it a neutral or a quiet brand tone at 4.5:1 on the background; the accent belongs to the primary action.
  • Dark page retone: ink #fafafa, muted and eyebrow #a1a1aa, hairline rgb(255 255 255 / 0.12), accent #fafafa, on-accent #18181b (all --hero-text-first-*), on a zinc-950 parent. usage.md has the snippet.
  • Type: the headline steps text-4xl, sm:text-6xl, lg:text-7xl and is held to max-w-[12em] so it sets as a two-line block. A headline over 60 characters (LONG_TITLE at the top of the script) steps down to text-3xl, sm:text-5xl, lg:text-6xl on max-w-[18em] so it reads as three or four lines; change the constant or the classes to move that point.
  • Links: replace each href with a real route and make each label name its destination ("Book an appointment", not "Learn more").
  • Headings: keep headingLevel at 1 when the hero opens the page, and set 2 when something above it already carries the h1.

Public CSS variables

VariableToken
--hero-text-first-accentaccent
--hero-text-first-on-accentonAccent
--hero-text-first-inkink
--hero-text-first-mutedmuted
--hero-text-first-hairlinehairline
--hero-text-first-eyebroweyebrow

Accessibility#

On this page
  • The title renders at the configured heading level (h1 by default) and names the section through aria-labelledby. Only one h1 per page is the consumer's responsibility; set headingLevel to 2 when the page already has one.
  • The eyebrow is a paragraph placed before the heading in DOM order, not inside it, so the heading's accessible name is the headline alone.
  • Both actions are real links with a two-pixel accent focus outline, offset by two pixels, on :focus-visible only. Link text must describe the destination.
  • Actions are at least 48 px tall at every width and span the full width below the sm breakpoint.
  • The secondary link's arrow is decorative and hidden from assistive technology; it trails the label's last word and points along the reading direction under dir="rtl".
  • White on-accent text measures about 17.7:1 on the neutral accent, 6.7:1 on blue and 7.7:1 on emerald at rest, and about 12.6:1, 4.8:1 and 5.5:1 on their hover shades. Muted text (#52525b) measures 7.7:1 on white.
  • The pressed scale and the arrow's hover nudge are removed under prefers-reduced-motion.
  • Letter-spacing resets for right-to-left scripts and CJK; under right-to-left the eyebrow drops its uppercase treatment and sets at 14 px, and the headline takes taller lines. Japanese headline, paragraph and note break at phrase boundaries where supported.
  • Element IDs come from $props.id(), so two heroes on one page stay unique.

Known limitations

  • Contrast ratios are computed for the shipped palettes on white; any changed token or background must be re-checked (4.5:1 for text, 3:1 for the focus ring).

Release details#

On this page
Integration
  • Presentational
  • Works without client-side JavaScript
  • Server-side rendering supported
Dependencies
No additional runtime packages beyond Svelte and Tailwind CSS
License

MIT. Default license approval is pending; see the license status before adopting the source.

Version history
  • 1.0.0 (Published) Current release · 30 September 2026

Only the current release is available. Keep downloaded source and its receipt if you need to use it again later.