Hero Block
Hero is a responsive marketing hero section built on MUI with headline copy, a primary call-to-action, and a large hero image in a row layout on large screens.
Installation
- npm
- yarn
- pnpm
- bun
npm install @nodeblocks/frontend-hero-block
yarn add @nodeblocks/frontend-hero-block
pnpm add @nodeblocks/frontend-hero-block
bun add @nodeblocks/frontend-hero-block
What You Need
| Item | Why it matters |
|---|---|
byline | The headline |
buttonText | Label on the CTA |
onClickButton | Handler when the CTA is clicked |
imageUrl | Source URL for image (2:1 aspect ratio expected) |
Pass copy and imagery from your page or CMS. Optional secondaryText and tertiaryText render below the byline and under the button. Use imageAlignment to adjust vertical placement of the image on large screens (top, center, or bottom).
Code Examples
Doc previews use a bounded frame and explicit Hero.Img sizing so the hero fits the live editor. In your app, use full-width layout and omit those preview-only styles.
- Quick Start
- Layouts
- Compound Components
- Block Override
function Example() { const imageUrl = '/img/undraw_docusaurus_mountain.svg'; return ( <Hero byline="Welcome to Our Platform" secondaryText="Streamline operations and boost productivity" tertiaryText="Free 30-day trial • No credit card required" buttonText="Get Started" imageUrl={imageUrl} onClickButton={() => {}} sx={{ '& .nbb-hero-content': { position: 'relative', zIndex: 1, maxWidth: '100%', }, '& .nbb-hero-content-byline, & .nbb-hero-content-secondary-text, & .nbb-hero-content-tertiary-text': { color: 'grey.900', textShadow: '0 0 8px rgba(255,255,255,1), 0 0 16px rgba(255,255,255,0.9), 0 1px 2px rgba(255,255,255,0.95)', }, '& .nbb-hero-content-tertiary-text': { color: 'primary.dark', }, }} /> ); }
Toggle imageAlignment to place the hero image toward the top, center, or bottom of the row on large screens.
function Example() { const [imageAlignment, setImageAlignment] = React.useState('center'); const imageUrl = '/img/undraw_docusaurus_react.svg'; const alignments = ['top', 'center', 'bottom']; return ( <Box sx={{ width: '100%', maxWidth: 720, mx: 'auto' }}> <Stack direction="row" spacing={1} sx={{ mb: 1.5, flexWrap: 'wrap' }}> {alignments.map((value) => ( <Button key={value} size="small" variant={imageAlignment === value ? 'contained' : 'outlined'} onClick={() => setImageAlignment(value)} > {value} </Button> ))} </Stack> <Box sx={{ border: '1px solid', borderColor: 'divider', borderRadius: 2, overflow: 'hidden', bgcolor: 'background.paper', }} > <Hero byline="Find your next role" secondaryText="Engineer-focused job search" buttonText="Browse jobs" imageUrl={imageUrl} imageAlignment={imageAlignment} onClickButton={() => {}} sx={{ '& .nbb-hero-content': { position: 'relative', zIndex: 1, maxWidth: '100%', }, '& .nbb-hero-content-byline, & .nbb-hero-content-secondary-text, & .nbb-hero-content-tertiary-text': { color: 'grey.900', textShadow: '0 0 8px rgba(255,255,255,1), 0 0 16px rgba(255,255,255,0.9), 0 1px 2px rgba(255,255,255,0.95)', }, '& .nbb-hero-content-tertiary-text': { color: 'primary.dark', }, }} /> </Box> </Box> ); }
imageAlignment applies in row layoutimageAlignment adjusts vertical placement of Hero.Img when the hero is a horizontal row (default at the lg breakpoint). The preview forces a row layout and mirrors alignment with alignSelf on Hero.Img so you can compare top, center, and bottom in the live editor.
Compose Hero.Content and Hero.Img directly and style each block with sx.
function Example() { const imageUrl = '/img/undraw_docusaurus_mountain.svg'; return ( <Box sx={{ width: '100%', maxWidth: 720, mx: 'auto', border: '1px solid', borderColor: 'divider', borderRadius: 2, overflow: 'hidden', bgcolor: 'background.paper', }} > <Hero byline="Welcome to Our Platform" secondaryText="Build faster with ready-made blocks" buttonText="Get Started" imageUrl={imageUrl} tertiaryText="Free 30-day trial • No credit card required" onClickButton={() => {}} sx={{ '& .nbb-hero-content': { position: 'relative', zIndex: 1, }, '& .nbb-hero-content-byline, & .nbb-hero-content-secondary-text, & .nbb-hero-content-tertiary-text': { color: 'grey.900', textShadow: '0 0 8px rgba(255,255,255,1), 0 0 16px rgba(255,255,255,0.9), 0 1px 2px rgba(255,255,255,0.95)', }, '& .nbb-hero-content-tertiary-text': { color: 'primary.dark', }, }} > <Hero.Content spacing={{ xs: 2, sm: 2.5 }} sx={{ p: { xs: 2, sm: 2.5 }, maxWidth: '100%', }} > <Stack sx={{ alignItems: 'center', width: '100%' }}> <Hero.Content.Byline /> <Hero.Content.SecondaryText /> </Stack> <Stack spacing={1.5} sx={{ alignItems: 'center', width: '100%' }}> <Hero.Content.ActionButton sx={{ px: 4, borderRadius: 99 }} /> <Hero.Content.TertiaryText sx={{ textAlign: 'center' }} /> </Stack> </Hero.Content> <Hero.Img src={imageUrl} /> </Hero> </Box> ); }
Use function children with defaultBlocks and defaultBlockOrder to replace blocks and control order. Place full-width UI inside the content block so it stays in the text column.
function Example() { const imageUrl = '/img/undraw_docusaurus_mountain.svg'; return ( <Box sx={{ width: '100%', maxWidth: 720, mx: 'auto', border: '1px solid', borderColor: 'divider', borderRadius: 2, overflow: 'hidden', bgcolor: 'background.paper', }} > <Hero byline="Welcome to Our Platform" secondaryText="Streamline operations and boost productivity" buttonText="Get Started" imageUrl={imageUrl} tertiaryText="Free 30-day trial • No credit card required" onClickButton={() => {}} sx={{ maxHeight: 'none', flexDirection: 'column', alignItems: 'center', gap: 2, py: 2, px: 1, '& .nbb-hero-content': { position: 'relative', zIndex: 1, }, '& .nbb-hero-content-byline, & .nbb-hero-content-secondary-text, & .nbb-hero-content-tertiary-text': { color: 'grey.900', textShadow: '0 0 8px rgba(255,255,255,1), 0 0 16px rgba(255,255,255,0.9), 0 1px 2px rgba(255,255,255,0.95)', }, '& .nbb-hero-content-tertiary-text': { color: 'primary.dark', }, }} > {({ defaultBlocks }) => ({ blocks: { ...defaultBlocks, content: ( <Hero.Content spacing={{ xs: 2, sm: 2.5 }} sx={{ p: { xs: 2, sm: 2.5 }, width: '100%' }}> <Alert severity="info" sx={{ width: '100%' }}> Limited-time offer: extended trial for new teams. </Alert> <Stack sx={{ alignItems: 'center', width: '100%' }}> <Hero.Content.Byline /> <Hero.Content.SecondaryText sx={{ fontSize: { xs: 24, sm: 32 }, lineHeight: 1.2, textAlign: 'center', }} /> </Stack> <Stack spacing={1.5} sx={{ alignItems: 'center', width: '100%' }}> <Hero.Content.ActionButton /> <Hero.Content.TertiaryText /> </Stack> </Hero.Content> ), img: <Hero.Img src={imageUrl} />, }, blockOrder: ['content', 'img'], })} </Hero> </Box> ); }
When to use block overrides
Default defaultBlockOrder is content, img, byline, secondaryText, actionButton, tertiaryText. The root render uses content and img; byline, secondaryText, actionButton, and tertiaryText are nested inside Hero.Content by default. Prepend banners or custom markup by overriding the content block (not as a sibling in the root row). Replace img when you need custom image styling while keeping imageUrl on the root for context.
Important Props
Core Props
| Prop | Type | Required | Default | Description |
|---|---|---|---|---|
byline | ReactNode | Yes | — | Main headline rendered by Hero.Content.Byline |
buttonText | string | Yes | — | Label on Hero.Content.ActionButton |
onClickButton | () => void | Yes | — | Called when the action button is clicked (also used when Hero.Content.ActionButton has no onClick) |
imageUrl | string | Yes | — | Image source passed to Hero.Img when src is not set on the sub-component |
Content Props
| Prop | Type | Required | Default | Description |
|---|---|---|---|---|
secondaryText | ReactNode | No | undefined | Large supporting line below the byline (Hero.Content.SecondaryText) |
tertiaryText | ReactNode | No | undefined | Smaller line below the action button (Hero.Content.TertiaryText) |
imageAlignment | 'top' | 'center' | 'bottom' | No | undefined | Vertical alignment of Hero.Img on large screens (lg breakpoint) |
Layout and Composition Props
| Prop | Type | Required | Default | Description |
|---|---|---|---|---|
children | ReactNode | function | No | undefined | Compound sub-components (Hero.Content, Hero.Img, …) or a function ({ defaultBlocks, defaultBlockOrder }) => ({ blocks, blockOrder }) to inject or reorder layout blocks |
className | string | No | undefined | Class name on the root stack (nbb-hero) |
sx | SxProps | No | undefined | MUI system styles for the root stack |
Hero inherits StackProps (except children). Default defaultBlockOrder: content, img, byline, secondaryText, actionButton, tertiaryText.
Sub-component props
Sub-components read from context and accept the same content keys as props to override locally.
| Sub-component | Main Props | Inherits | Built on |
|---|---|---|---|
Hero.Content | children, spacing (default { xs: 3, lg: 4 }), className, sx | StackProps | Stack |
Hero.Img | src, alt (default hero image), imageUrl, imageAlignment, component, className, sx | BoxProps | Box (component="img") |
Hero.Content.Byline | byline, children, variant (default h3), className, sx | TypographyProps | Typography |
Hero.Content.SecondaryText | secondaryText, children, variant (default h2), className, sx | TypographyProps | Typography |
Hero.Content.ActionButton | buttonText, onClickButton, onClick, children, variant (default contained), className, sx | ButtonProps | Button |
Hero.Content.TertiaryText | tertiaryText, children, variant (default h6), color (default primary), className, sx | TypographyProps | Typography |
Default UI Blocks
| Block | Built on | Notes |
|---|---|---|
Hero (root) | Stack | Row on lg, centered, max height capped (nbb-hero) |
content (Hero.Content) | Stack | Text column with byline group and CTA group (nbb-hero-content) |
img (Hero.Img) | Box | Renders imageUrl; alt defaults to hero image (nbb-hero-img) |
byline (Hero.Content.Byline) | Typography | Responsive heading from context byline |
secondaryText (Hero.Content.SecondaryText) | Typography | Large centered line when secondaryText is set |
actionButton (Hero.Content.ActionButton) | Button | Contained CTA using context buttonText |
tertiaryText (Hero.Content.TertiaryText) | Typography | Primary-colored line when tertiaryText is set |
Default root render order: content → img.
TypeScript
import * as React from 'react';
import { Hero } from '@nodeblocks/frontend-hero-block';
import type { ReactNode } from 'react';
type HeroSectionProps = {
byline: ReactNode;
buttonText: string;
imageUrl: string;
onClickButton: () => void;
secondaryText?: ReactNode;
tertiaryText?: ReactNode;
imageAlignment?: 'top' | 'center' | 'bottom';
};
export function HeroSection({
byline,
buttonText,
imageUrl,
onClickButton,
secondaryText,
tertiaryText,
imageAlignment,
}: HeroSectionProps) {
return (
<Hero
byline={byline}
buttonText={buttonText}
imageUrl={imageUrl}
onClickButton={onClickButton}
secondaryText={secondaryText}
tertiaryText={tertiaryText}
imageAlignment={imageAlignment}
/>
);
}