Example Weaverse Components
This guide provides real-world examples of Weaverse components using modern React 19 patterns, showcasing different implementation approaches and best practices. Each example includes the complete implementation, schema definition, and explanations of key concepts - all updated to work seamlessly with React Router v7 and React 19's improved capabilities.
Table of Contents
Hero Image
A versatile hero section component that supports different heights, content positions, and background styles.
Implementation
// app/sections/hero-image.tsximport { createSchema, IMAGES_PLACEHOLDERS, useThemeSettings,} from "@weaverse/hydrogen";import type { VariantProps } from "class-variance-authority";import { cva } from "class-variance-authority";import { backgroundInputs } from "~/components/background-image";import { overlayInputs } from "~/components/overlay";import type { SectionProps } from "~/components/section";import { Section, layoutInputs } from "~/components/section";
// Define variants using CVAconst variants = cva("flex flex-col [&_.paragraph]:mx-[unset]", { variants: { height: { small: "min-h-[40vh] lg:min-h-[50vh]", medium: "min-h-[50vh] lg:min-h-[60vh]", large: "min-h-[70vh] lg:min-h-[80vh]", full: "", }, enableTransparentHeader: { true: "", false: "", }, contentPosition: { "top left": "justify-start items-start [&_.paragraph]:[text-align:left]", "top center": "justify-start items-center [&_.paragraph]:[text-align:center]", "top right": "justify-start items-end [&_.paragraph]:[text-align:right]", "center left": "justify-center items-start [&_.paragraph]:[text-align:left]", "center center": "justify-center items-center [&_.paragraph]:[text-align:center]", "center right": "justify-center items-end [&_.paragraph]:[text-align:right]", "bottom left": "justify-end items-start [&_.paragraph]:[text-align:left]", "bottom center": "justify-end items-center [&_.paragraph]:[text-align:center]", "bottom right": "justify-end items-end [&_.paragraph]:[text-align:right]", }, }, compoundVariants: [ { height: "full", enableTransparentHeader: true, className: "h-screen", }, { height: "full", enableTransparentHeader: false, className: "h-screen-no-nav", }, ], defaultVariants: { height: "large", contentPosition: "center center", },});
export interface HeroImageProps extends VariantProps<typeof variants> {}
function HeroImage(props: HeroImageProps & SectionProps) { const { children, height, contentPosition, ...rest } = props; const { enableTransparentHeader } = useThemeSettings(); return ( <Section {...rest} containerClassName={variants({ contentPosition, height, enableTransparentHeader, })} > {children} </Section> );}
export default HeroImage;
export let schema = createSchema({ type: "hero-image", title: "Hero image", settings: [ { group: "Layout", inputs: [ { type: "select", name: "height", label: "Section height", configs: { options: [ { value: "small", label: "Small" }, { value: "medium", label: "Medium" }, { value: "large", label: "Large" }, { value: "full", label: "Fullscreen" }, ], }, }, { type: "position", name: "contentPosition", label: "Content position", defaultValue: "center center", }, ...layoutInputs.filter( (inp) => inp.name !== "divider" && inp.name !== "borderRadius", ), ], }, { group: "Background", inputs: [ ...backgroundInputs.filter( (inp) => inp.name !== "backgroundFor" && inp.name !== "backgroundColor", ), ], }, { group: "Overlay", inputs: overlayInputs }, ], childTypes: ["subheading", "heading", "paragraph", "button"], presets: { height: "large", contentPosition: "center center", backgroundImage: IMAGES_PLACEHOLDERS.banner_1, backgroundFit: "cover", enableOverlay: true, overlayOpacity: 40, children: [ { type: "subheading", content: "Subheading", color: "#ffffff", }, { type: "heading", content: "Hero image with text overlay", as: "h2", color: "#ffffff", size: "default", }, { type: "paragraph", content: "Use this text to share information about your brand with your customers.", color: "#ffffff", }, ], },});
Key Features
-
CVA Variants:
- Maps schema select inputs to CSS classes
- Handles responsive design through variant definitions
- Uses compound variants for special cases
-
Schema Design:
- Groups related inputs logically
- Provides sensible defaults
- Supports child components
- Includes preset content
-
Component Structure:
- Modern React 19 function component pattern
- Leverages theme settings
- Maintains clean prop handling
- Supports flexible content positioning
Featured Product
A component that displays a featured product with its details and purchase options.
Implementation
// app/sections/featured-product/index.tsximport type { ComponentLoaderArgs, HydrogenComponentProps } from '@weaverse/hydrogen';import { createSchema } from '@weaverse/hydrogen';import { PRODUCT_QUERY } from '~/graphql/queries';
interface FeaturedProductData { productHandle: string;}
export const loader = async (args: ComponentLoaderArgs<FeaturedProductData>) => { const { weaverse, data } = args; const { storefront } = weaverse; if (!data?.productHandle) { return null; } const { product } = await storefront.query(PRODUCT_QUERY, { variables: { handle: data.productHandle, }, });
return { product };};
type FeaturedProductProps = HydrogenComponentProps<Awaited<ReturnType<typeof loader>>> & FeaturedProductData;
function FeaturedProduct(props: FeaturedProductProps) { const { loaderData, productHandle, ...rest } = props; const product = loaderData?.product; if (!product) { return <div>Select a product in the editor</div>; } return ( <section {...rest}> <h2>{product.title}</h2> <p>{product.description}</p> {/* Product details */} </section> );}
export default FeaturedProduct;
export let schema = createSchema({ type: 'featured-product', title: 'Featured Product', settings: [ { group: 'Product', inputs: [ { type: 'product', name: 'productHandle', label: 'Product', }, ], }, ],});
Key Features
-
Data Loading:
- Uses component loader for product data
- Handles missing product gracefully
- Type-safe data fetching
-
Schema Design:
- Simple product selection input
- Clear grouping of related settings
-
Component Structure:
- Proper error handling
- Clean prop destructuring
- Type-safe props interface
- Modern React 19 function component pattern
Team Members
A component that displays team members with their profiles and social links.
Implementation
// app/sections/our-team/team-members.tsximport { GithubLogo, LinkedinLogo, XLogo } from "@phosphor-icons/react";import { Link } from "react-router";import { type HydrogenComponentProps, type HydrogenComponentSchema, type WeaverseImage, useParentInstance,} from "@weaverse/hydrogen";import { forwardRef } from "react";import type { OurTeamQuery } from "storefront-api.generated";import { Image } from "~/components/image";
type MemberType = { name: string; title: string; bio: string; avatar: WeaverseImage; github_url: string; linkedin_url: string; x_url: string;};
function TeamMembers(props: HydrogenComponentProps) { const parent = useParentInstance(); const { metaobjects }: OurTeamQuery = parent.data.loaderData || {}; if (metaobjects?.nodes?.length) { const members = metaobjects.nodes; return ( <div {...props} className="grid gap-8 mb-6 lg:mb-16 md:grid-cols-2 pt-4" > {members.map(({ id, fields }) => { const member: Partial<MemberType> = {}; for (const { key, value, reference } of fields) { // @ts-ignore member[key] = key === "avatar" ? reference?.image : value; } const { name, title, bio, avatar, github_url, linkedin_url, x_url } = member; return ( <div key={id} className="items-center bg-gray-50 sm:flex"> {avatar && ( <Image data={avatar} sizes="auto" className="w-full h-auto sm:w-48 sm:h-48" aspectRatio="1/1" width={500} /> )} <div className="p-5"> <div className="text-xl font-semibold tracking-tight"> {name} </div> <span className="text-gray-600">{title}</span> {bio && ( <p className="mt-3 mb-4 font-light text-gray-600">{bio}</p> )} <ul className="flex space-x-3"> {linkedin_url && ( <li> <Link to={linkedin_url} target="_blank" className="text-gray-500 hover:text-gray-900" > <LinkedinLogo className="w-6 h-6" /> </Link> </li> )} {github_url && ( <li> <Link to={github_url} target="_blank" className="text-gray-500 hover:text-gray-900" > <GithubLogo className="w-6 h-6" /> </Link> </li> )} {x_url && ( <li> <Link to={x_url} target="_blank" className="text-gray-500 hover:text-gray-900" > <XLogo className="w-6 h-6" /> </Link> </li> )} </ul> </div> </div> ); })} </div> ); } return <div {...props} />; }}
export default TeamMembers;
export let schema = createSchema({ type: "team-members", title: "Team Members", settings: [ { group: "Layout", inputs: [ { type: "select", name: "layout", label: "Layout", configs: { options: [ { value: "grid", label: "Grid" }, { value: "list", label: "List" }, ], }, defaultValue: "grid", }, ], }, ],});
Key Features
-
Data Integration:
- Uses parent instance data
- Handles metaobjects data structure
- Maps fields to component properties
-
UI Components:
- Responsive image handling
- Social media icons
- Flexible layout options
-
Accessibility:
- Semantic HTML structure
- Proper link attributes
- Clear visual hierarchy
- Modern React 19 function component pattern
Review List
A component that displays product reviews with ratings and filtering options.
Implementation
// app/sections/ali-reviews/review-list.tsximport { type HydrogenComponentProps, type HydrogenComponentSchema, useParentInstance,} from "@weaverse/hydrogen";import { forwardRef } from "react";import { StarRating } from "~/components/star-rating";import type { AliReviewsLoaderData } from ".";import { ReviewBar } from "./review-bar";import type { AliReview, ReviewItemData } from "./review-item";import { ReviewItem } from "./review-item";
type AliReviewsProps = ReviewItemData & { showAvgRating: boolean; showReviewsCount: boolean; showReviewsProgressBar: boolean; reviewsToShow: number; showReviewWithMediaOnly: boolean;};
function ReviewList(props: AliReviewsProps & HydrogenComponentProps) { const { children, showAvgRating, showReviewsCount, showReviewsProgressBar, reviewsToShow, showReviewWithMediaOnly, showCountry, showDate, showVerifiedBadge, verifiedBadgeText, showStar, ...rest } = props; const parent = useParentInstance(); const allReviews: AliReviewsLoaderData = parent.data.loaderData; if (allReviews?.length) { const { totalReviews, avgRating, reviewsByRating } = getReviewsSummary(allReviews); let reviewsToRender = Array.from(allReviews); if (showReviewWithMediaOnly) { reviewsToRender = reviewsToRender.filter((rv) => rv.media.length > 0); } reviewsToRender = reviewsToRender.slice(0, reviewsToShow);
return ( <div {...rest} className="md:flex md:gap-16 space-y-8 md:space-y-0" > <div className="my-6 space-y-6 md:my-8 shrink-0" data-motion="slide-in"> <div className="shrink-0 flex gap-4"> {showAvgRating && ( <div className="text-6xl font-bold leading-none"> {avgRating.toFixed(1)} </div> )} <div className="flex flex-col gap-1.5 justify-center"> <StarRating rating={avgRating} /> {showReviewsCount && ( <div className="text-sm font-medium leading-none text-gray-500"> {totalReviews} reviews </div> )} </div> </div> {showReviewsProgressBar && ( <div className="mt-6 min-w-0 flex-1 space-y-3 sm:mt-0"> {Object.entries(reviewsByRating) .sort((a, b) => Number(b[0]) - Number(a[0])) .map(([rating, ratingData]) => ( <ReviewBar key={rating} rating={Number(rating)} {...ratingData} /> ))} </div> )} </div> <div className="mt-6 divide-y divide-gray-200 grow" data-motion="slide-in" > {reviewsToRender.map((review) => ( <ReviewItem key={review.id} review={review} showCountry={showCountry} showDate={showDate} showVerifiedBadge={showVerifiedBadge} verifiedBadgeText={verifiedBadgeText} showStar={showStar} /> ))} </div> </div> ); } return <div {...rest} />;}
export default ReviewList;
export let schema = createSchema({ type: "review-list", title: "Review List", settings: [ { group: "Display", inputs: [ { type: "toggle", name: "showAvgRating", label: "Show average rating", defaultValue: true, }, { type: "toggle", name: "showReviewsCount", label: "Show reviews count", defaultValue: true, }, { type: "toggle", name: "showReviewsProgressBar", label: "Show rating distribution", defaultValue: true, }, { type: "number", name: "reviewsToShow", label: "Number of reviews to show", defaultValue: 5, }, { type: "toggle", name: "showReviewWithMediaOnly", label: "Show reviews with media only", defaultValue: false, }, ], }, ],});
Key Features
-
Data Processing:
- Calculates review statistics
- Filters and sorts reviews
- Handles media content
-
UI Components:
- Star rating display
- Review progress bars
- Responsive layout
-
Configuration:
- Flexible display options
- Customizable review count
- Media filtering
- Modern React 19 function component pattern
Image with Text
A versatile component that combines images with text content in various layouts.
Implementation
// app/sections/image-with-text/index.tsximport type { HydrogenComponentProps, HydrogenComponentSchema } from '@weaverse/hydrogen';import { ImageWithTextContent } from './content';import { ImageWithTextImage } from './image';
interface ImageWithTextProps extends HydrogenComponentProps { imagePosition: 'left' | 'right'; imageWidth: 'small' | 'medium' | 'large';}
function ImageWithText(props: ImageWithTextProps) { const { children, imagePosition, imageWidth, ...rest } = props; return ( <section {...rest} className={`flex flex-col md:flex-row gap-8 ${ imagePosition === 'right' ? 'md:flex-row-reverse' : '' }`} > <ImageWithTextImage width={imageWidth} /> <ImageWithTextContent>{children}</ImageWithTextContent> </section> );}
export default ImageWithText;
export let schema = createSchema({ type: 'image-with-text', title: 'Image with Text', settings: [ { group: 'Layout', inputs: [ { type: 'select', name: 'imagePosition', label: 'Image position', configs: { options: [ { value: 'left', label: 'Left' }, { value: 'right', label: 'Right' }, ], }, defaultValue: 'left', }, { type: 'select', name: 'imageWidth', label: 'Image width', configs: { options: [ { value: 'small', label: 'Small' }, { value: 'medium', label: 'Medium' }, { value: 'large', label: 'Large' }, ], }, defaultValue: 'medium', }, ], }, ], childTypes: ['subheading', 'heading', 'paragraph', 'button'],});
Key Features
-
Layout Options:
- Flexible image positioning
- Adjustable image width
- Responsive design
-
Component Structure:
- Separated content and image components
- Clean prop handling
- Type-safe interfaces
- Modern React 19 function component pattern
-
Schema Design:
- Logical input grouping
- Child component support
- Sensible defaults
Conclusion
These examples demonstrate various patterns and best practices for building Weaverse components with modern React 19:
- Data Integration: Different approaches to fetching and handling data
- Component Structure: Clean, maintainable component organization using modern function components
- Schema Design: Intuitive and flexible configuration options
- Styling: Responsive and customizable layouts with CVA variants
- Accessibility: Semantic HTML and proper ARIA attributes
- Modern React Patterns: No forwardRef needed, clean function components, optimal TypeScript integration
Use these examples as inspiration for your own components, adapting the patterns to your specific needs while maintaining the core principles of clean, maintainable, and user-friendly component design with React 19's modern capabilities.