Stack these inside <Base>, between <Header> and <Footer>, in whatever order tells your product's story.
See them all assembled on a real page at /test, which is also where to check
how a component actually looks, since the examples below are code-only.
<Hero><LogoStrip><FeatureSplit><Testimonials><AllFeaturesAccordion><FAQ><FullTrialSignup><SpotlightSplit>The first section of a marketing homepage: a large title, an optional badge and subtitle, one or
more call-to-action buttons, and a dotted background pattern. Place it as the first child inside <Base>, right after <Header>.
titlesubtitlestring | Snippet shape as title.badge"Open-source & free to try". Also string | Snippet.buttonsnull{ href, label, external?, size?, variant?, ... }. Any other prop <Button> accepts is passed through.accentColorvar(--accent).hds-hl highlight color, and the scroll
dot, in light mode.accentColorDarkaccentColoraccentColor, used in dark mode. Falls back to accentColor, then the theme accent.fullHeightfalsetrue, the hero fills the viewport height below the header instead of sizing
to its content.after<Hero
badge="Open-source & free to try"
buttons={{ href: '/console?signup', label: 'Start your blog' }}
>
{#snippet title()}
You Publish.<br /><span class="heading-small">(We'll handle the rest)</span>
{/snippet}
{#snippet subtitle()}
Hyvor Blogs is our <span class="hds-marker">blogging platform</span>. It's
<span class="hds-hl">fast</span>, <span class="hds-hl">simple</span>, and
<span class="hds-hl">open-source</span>!
{/snippet}
</Hero>Two helper classes are styled globally inside the title/subtitle: .hds-hl (accent-colored text) and .hds-marker (a highlighter-style background). Wrap the part
you want emphasized in one of these, inside a {#snippet} as shown above.
If the text is translated, keep the <span> wrapping in code and let only the
plain text be translated: with Intl.T's element param, translators never write markup:
{#snippet title()}
<Intl.T key="hero.title" params={{ hl: { element: 'span', props: { class: 'hds-hl' } } }} />
{/snippet}with the locale file holding only plain text and a bare tag name:
{
"hero.title": "You <hl>Publish</hl>."
}A strip of customer/partner logos, usually placed right under Hero. By default
it scrolls as an infinite marquee (pauses on hover, and falls back to a static
wrapped row for prefers-reduced-motion); set marquee=false for a plain wrapped row instead. Logos are dimmed and forced to a single color by default (invert), so a mixed set of brand marks reads as one consistent row instead of competing with each
other; opt individual opaque/full-color logos out with color.
logos{ name, src, href?, width?, height?, color? }. width/height default to 120/28 per logo, matching each logo's own aspect ratio.
Set color on a logo to keep it in its real colors regardless of invert (e.g. an opaque, full-color app icon that would otherwise crush into a blank box under the white
silhouette treatment).label''"Trusted by 100+ businesses".backgroundvar(--accent)rgb()/hsl(), etc.inverttruecolor) to solid white via filter, for a colored/dark background. Turn off on a light/neutral
background to show logos in their original colors.marqueetruefalse for a plain centered row that wraps onto multiple lines instead.speed34marquee is on.<LogoStrip
label="Trusted by 10,000+ websites & businesses"
logos={[
{ name: 'Elementor', src: '/customers/elementor.png' },
{ name: 'Stanford University', src: '/customers/stanford.png', width: 125, height: 40 },
{ name: 'ACCA', src: '/customers/acca.png', width: 44, height: 44, color: true },
{ name: 'El Debate', src: '/customers/eldebate.png', width: 140, height: 28 }
]}
/>A two-column section: text (eyebrow, title, description, bullets, a button) on one side and a
visual on the other. Stack a few of these to walk through a product's main features, alternating flip to keep the page from feeling one-sided.
eyebrow"Post Editor". Required unless left is used.titledescriptionbullets[]interactiveBullets only applies to the string[] form.buttonnull{ href, label, external? } configs, rendered as outline buttons.visualactiveBullet: number when interactiveBullets is on, so the visual can
switch state to match the selected bullet.interactiveBulletsfalseflipfalsealtBgfalseoverlapfalsemaxfalsehds-container-max instead of the default hds-container.lefteyebrow/title/description/bullets/button aren't flexible enough.after<FeatureSplit
eyebrow="Post Editor"
title="Write without friction"
description="A clean, distraction-free editor with everything you need to write and format posts."
bullets={[
'Markdown shortcuts for all the formatting you need',
'Paste, drag and drop, or pick images from Unsplash',
'Syntax highlighting for 100+ languages'
]}
button={{ href: '/console?signup', label: 'Start writing' }}
interactiveBullets
>
{#snippet visual(activeBullet)}
<div class="visual-placeholder hds-box">
<IconImage size={40} />
<span>Active bullet: {activeBullet + 1}</span>
</div>
{/snippet}
</FeatureSplit>Pass a Snippet as bullets to render something other than a checklist in that spot, here,
a small stat row.
<FeatureSplit
eyebrow="Optimizations"
title="Built for speed"
description="All official themes are optimized for speed."
>
{#snippet bullets()}
<div class="stats">
<div><strong>99</strong><span>Lighthouse score</span></div>
<div><strong><200ms</strong><span>Time to first byte</span></div>
</div>
{/snippet}
{#snippet visual()}
<div class="visual-placeholder hds-box">
<IconImage size={40} />
</div>
{/snippet}
</FeatureSplit>A horizontally-scrolling row of customer quotes and (optionally) video reviews. There's no limit
on how many reviews you pass in: once they don't all fit on screen, the row becomes swipeable:
touch/trackpad swipe and mouse drag both work, and it snaps to each card as you scroll.
reviewslabel"Testimonials"title"Loved by our customers."handwrittenNamestruereviews shapetype'text' or 'video'.namerolequote'text'videoUrl'video'posterUrl'video'<Testimonials
reviews={[
{
type: 'text',
name: 'Lionel S.',
role: 'Blogger',
quote:
'I need a simple, easy-to-use, fast, beautiful and mature blogging tool that resolves the WordPress bloat. Hyvor Blogs handles this beautifully.'
},
{
type: 'video',
name: 'Video testimonial',
role: 'Coming soon'
},
{
type: 'text',
name: 'Manoj P.',
role: 'Senior Application Engineer',
quote:
'The platform offers a seamless and user-friendly experience for both bloggers and readers. The customization options are extensive.'
}
]}
/>A catch-all "and there's more" section: categories of features, each expandable into a grid of icon + title + description cards. Good for listing everything that didn't get its own Feature Split section.
categoriestitle"And There's More..."description''defaultOpenIndex0-1 to start with all of them collapsed.accentColorvar(--accent)color, which only tints that category's icon
badge and its features' watermarks.accentColorDarkaccentColoraccentColor, used in dark mode. Falls back to accentColor, then the theme accent.categories shapelabelicon@hyvor/icons component for the trigger.color'var(--green)' or '#4b874b', defaults
to the theme accent.features{ icon, title, description }, one card per feature.<AllFeaturesAccordion
categories={[
{
label: 'Post Editor',
icon: IconPencil,
color: 'var(--green)',
features: [
{
icon: IconPencil,
title: 'All the basics',
description: 'Bold, italic, headings, lists, quotes, links, and more.'
},
{
icon: IconImage,
title: 'Images',
description: 'Upload, paste, drag and drop, and more ways to add images.'
}
]
},
{
label: 'Your Blog',
icon: IconSignpost2,
color: 'var(--blue)',
features: [
{ icon: IconTag, title: 'Tags', description: 'Organize your posts with tags.' },
{
icon: IconRss,
title: 'Atom (RSS) Feed',
description: 'Atom feeds are generated automatically.'
}
]
}
]}
/>A two-column accordion of frequently asked questions. Unlike the other homepage sections, this one
isn't full-bleed, so wrap it in your own heading and a .hds-container, as shown in
the example.
items{ q, a }. a is rendered as HTML, and used verbatim
as the rich schema's answer text.columns21 or 2. Items are split left-to-right, top-to-bottom within each
column. Regardless of this setting, the layout always collapses to a single column below
768px, so mobile visitors never see a cramped two-column grid.richSchematrueFAQPage JSON-LD rich schema into <svelte:head>,
generated straight from items, for Google's FAQ rich results.toggleIconBgvar(--accent-light)<div class="hds-container">
<h2>Frequently asked questions</h2>
<FAQ
items={[
{ q: 'Is there a free trial?', a: 'Yes, every plan starts with a 14-day free trial.' },
{ q: 'Can I cancel anytime?', a: 'Yes, there are no long-term contracts.' },
{
q: 'Do you offer discounts?',
a: 'Yes, see our <a href="/pricing">pricing page</a> for details.'
}
]}
/>
</div>A closing call-to-action section: a title, a description, a button, and a row of reassurance checks (e.g. "No credit card required"). Use it as the last section before the footer.
title"Start your trial today"description''badgenullbuttonnull{ href, label, external? } configs, rendered as large buttons.checks['14-day free trial', 'No credit card required', 'Cancel anytime']accentColorvar(--accent)accentColorDarkaccentColoraccentColor, used in dark mode. Falls back to accentColor, then the theme accent.Any other prop passed in (e.g. id or class) is forwarded to the root <section>.
<FullTrialSignup
badge="Open-source & free to try"
title="Start your blog today"
description="Join hundreds of bloggers and businesses building with Hyvor Blogs."
button={{ href: '/console?signup', label: 'Start your blog →' }}
/><FullTrialSignup
accentColor="var(--red)"
title="Start your blog today"
button={{ href: '/console?signup', label: 'Start your blog →' }}
/>A dark, full-width section with an animated grid background that lights up around the cursor: a
text column (title, description, a small stat row, a button) on one side, and a free-form content snippet on the other. It isn't tied to any one topic: the content side can hold
compliance seals, a logo wall, a quote, stat cards, or anything else that fits a dark panel.
titlestring | Snippet.description''stats[]"99.9% Uptime".buttonnull{ href, label, external? } configs, rendered as outline buttons.contentreversefalsebackgroundvar(--accent)"#574443"),
another theme variable ("var(--red)"), rgb()/hsl(),
etc.highlightColorrgba(255, 238, 217, 0.9)background.The use case this section was first built for: an enterprise/security callout. The seal badges (GdprSeal, CcpaSeal, SsoSeal, IsoSeal) ship as ready-made presets
built on the generic Seal component, which takes a size, an optional pending state (dashed border, muted colors, e.g. for a certification
that's still in progress), and any content as children.
<SpotlightSplit
title="Enterprise-ready."
description="Enterprise-grade security and compliance, without adding complexity to your publishing workflow."
stats={['> 99.9% Uptime', 'Priority Support']}
button={{ href: 'https://hyvor.com/enterprise', label: 'Contact Sales', external: true }}
background="#574443"
>
{#snippet content()}
<div class="seal-grid">
<GdprSeal />
<CcpaSeal />
<SsoSeal />
<IsoSeal />
</div>
{/snippet}
</SpotlightSplit>The same section works for unrelated content too, here a single pull quote, with reverse putting it on the left.
<SpotlightSplit
title="Built by writers, for writers."
description="Every part of the editor was shaped by feedback from people who publish daily."
button={{ href: '/console?signup', label: 'Start writing' }}
background="#2b2440"
highlightColor="rgba(216, 200, 255, 0.9)"
reverse
>
{#snippet content()}
<div class="quote hds-box">
<IconQuote size={22} />
<p>"The fastest editor I've used for long-form posts, by a wide margin."</p>
<span>A Hyvor Blogs user</span>
</div>
{/snippet}
</SpotlightSplit>