Getting Started
Welcome to Haus Tech's headless storefront ecosystem! This guide will help you get up and running with our components, libraries, and tools.
Overview
Haus Storefront Components is a collection of headless, cross-platform React components and hooks designed for building e-commerce storefronts. The library is organized into several categories:
- Core Libraries: Foundation functionality including SDKs, providers, and data management
- Common Libraries: Shared utilities, hooks, and UI components
- Store Components: E-commerce specific components (cart, product list, checkout, etc.)
- Plugin Configurations: Platform integrations and extensions
Prerequisites
Before you begin, make sure you have:
- Node.js version 18 or higher
- React 19 or higher
- TypeScript (recommended for type safety)
- Access to Haus Tech's private npm registry (contact the Haus Tech Team for access)
- A Vendure backend API endpoint (Vendure is our default provider - see Vendure Integration for details)
Quick Start
1. Install Core and Providers
Install @haus-storefront-react/core and @haus-storefront-react/providers. Core supplies DataProvider, SDKs, and hooks; the providers package includes the Vendure preset builder.
- npm
- Yarn
npm install @haus-storefront-react/core @haus-storefront-react/providers
yarn add @haus-storefront-react/core @haus-storefront-react/providers
Note: The packages are not publicly available. Contact the Haus Tech Team for access to the private npm registry.
2. Set Up the DataProvider
Wrap your application with DataProvider and a Vendure preset. This wires the SDK, query client, interceptors, and platform detection to all child components.
- React
- React Native
import { DataProvider } from '@haus-storefront-react/core'
import { VendureProviderPreset } from '@haus-storefront-react/providers/vendure'
const preset = new VendureProviderPreset({
apiUrl: 'https://your-vendure-api.com/shop-api',
defaultChannelToken: 'YOUR_CHANNEL_TOKEN',
locale: 'en',
currency: 'USD',
persistOptions: {
enabled: false,
},
}).toPreset()
function App() {
return (
<DataProvider platform='web' preset={preset}>
<YourApplication />
</DataProvider>
)
}
import { DataProvider } from '@haus-storefront-react/core'
import { VendureProviderPreset } from '@haus-storefront-react/providers/vendure'
const preset = new VendureProviderPreset({
apiUrl: 'https://your-vendure-api.com/shop-api',
defaultChannelToken: 'YOUR_CHANNEL_TOKEN',
locale: 'en',
currency: 'USD',
persistOptions: {
enabled: false,
},
}).toPreset()
function App() {
return (
<DataProvider platform='native' preset={preset}>
<YourApplication />
</DataProvider>
)
}
3. Use Core Hooks
Once the DataProvider is set up, you can use hooks throughout your application:
- React
- React Native
import { useSdk, useQuery } from "@haus-storefront-react/core";
function ProductList() {
const sdk = useSdk();
const { data, isLoading, error } = useQuery({
queryKey: ["products"],
queryFn: () => sdk.search({ take: 10 }),
});
if (isLoading) return <div>Loading...</div>;
if (error) return <div>Error: {error.message}</div>;
if (!data) return <div>No products found</div>;
return (
<div>
{data.items.map((product) => (
<div key={product.id}>
<h3>{product.name}</h3>
<p>{product.description}</p>
</div>
))}
</div>
);
}
import { useSdk, useQuery } from "@haus-storefront-react/core";
import { FlatList, Text, View } from "react-native";
function ProductList() {
const sdk = useSdk();
const { data, isLoading, error } = useQuery({
queryKey: ["products"],
queryFn: () => sdk.search({ take: 10 }),
});
if (isLoading) {
return (
<View>
<Text>Loading...</Text>
</View>
);
}
if (error) {
return (
<View>
<Text>Error: {error.message}</Text>
</View>
);
}
if (!data) {
return (
<View>
<Text>No products found</Text>
</View>
);
}
return (
<FlatList
data={data.items}
keyExtractor={(product) => product.id}
renderItem={({ item: product }) => (
<View>
<Text>{product.name}</Text>
<Text>{product.description}</Text>
</View>
)}
/>
);
}
Basic Examples
Examples assume a
vendurePresetfromVendureProviderPreset(see Quick Start).
Example 1: Shopping Cart
- React
- React Native
import { DataProvider } from "@haus-storefront-react/core";
import { VendureProviderPreset } from "@haus-storefront-react/providers/vendure";
import { Cart } from "@haus-storefront-react/cart";
import { AddItemToOrder } from "@haus-storefront-react/add-item-to-order";
const vendurePreset = new VendureProviderPreset({
apiUrl: 'https://your-api.com/shop-api',
defaultChannelToken: 'YOUR_TOKEN',
}).toPreset();
function ShoppingApp() {
return (
<DataProvider platform='web' preset={vendurePreset}>
<ProductPage />
<CartDisplay />
</DataProvider>
); }
function ProductPage() {
return (
<div>
<h1>Product Name</h1>
<AddItemToOrder.Root productVariantId='123'>
<AddItemToOrder.QuantityInput />
<AddItemToOrder.AddButton>Add to Cart</AddItemToOrder.AddButton>
</AddItemToOrder.Root>
</div>
); }
function CartDisplay() {
return (
<Cart.Root>
{({ data: order, isLoading }) => (
<>
{isLoading && <div>Loading cart...</div>}
{order && (
<>
<Cart.Items />
<Cart.Totals />
<Cart.CheckoutButton>Checkout</Cart.CheckoutButton>
</>
)}
<Cart.Empty>
<p>Your cart is empty</p>
</Cart.Empty>
</>
)}
</Cart.Root>
); }
import { DataProvider } from "@haus-storefront-react/core";
import { VendureProviderPreset } from "@haus-storefront-react/providers/vendure";
import { Cart } from "@haus-storefront-react/cart";
import { AddItemToOrder } from "@haus-storefront-react/add-item-to-order";
import {
Pressable,
SafeAreaView,
ScrollView,
Text,
View,
} from "react-native";
const vendurePreset = new VendureProviderPreset({
apiUrl: "https://your-api.com/shop-api",
defaultChannelToken: "YOUR_TOKEN",
}).toPreset();
function ShoppingApp() {
return (
<DataProvider platform='native' preset={vendurePreset}>
<SafeAreaView>
<ScrollView>
<ProductPage />
<CartDisplay />
</ScrollView>
</SafeAreaView>
</DataProvider>
);
}
function ProductPage() {
return (
<View>
<Text>Product Name</Text>
<AddItemToOrder.Root productVariantId='123'>
<AddItemToOrder.QuantityInput />
<AddItemToOrder.AddButton asChild>
<Pressable>
<Text>Add to Cart</Text>
</Pressable>
</AddItemToOrder.AddButton>
</AddItemToOrder.Root>
</View>
);
}
function CartDisplay() {
return (
<Cart.Root>
{({ data: order, isLoading }) => (
<View>
{isLoading && <Text>Loading cart...</Text>}
{order && (
<>
<Cart.Items />
<Cart.Totals />
<Cart.CheckoutButton asChild>
<Pressable>
<Text>Checkout</Text>
</Pressable>
</Cart.CheckoutButton>
</>
)}
<Cart.Empty>
<Text>Your cart is empty</Text>
</Cart.Empty>
</View>
)}
</Cart.Root>
);
}
Example 2: Product List with Search
- React
- React Native
import { DataProvider } from "@haus-storefront-react/core";
import { VendureProviderPreset } from "@haus-storefront-react/providers/vendure";
import { ProductList } from "@haus-storefront-react/product-list";
import { Search } from "@haus-storefront-react/search";
const vendurePreset = new VendureProviderPreset({
apiUrl: "https://your-api.com/shop-api",
defaultChannelToken: "YOUR_TOKEN",
}).toPreset();
function Storefront() {
return (
<DataProvider platform='web' preset={vendurePreset}>
<Search.Root>
<Search.Input placeholder='Search products...' />
<Search.Results>
<Search.Products />
</Search.Results>
</Search.Root>
<ProductList.Root>
<ProductList.Items>
{({ items }) => (
<div>
{items.map((item) => (
<ProductList.Item key={item.id} product={item}>
<ProductList.Image />
<ProductList.Name />
<ProductList.Price />
<ProductList.AddToCartButton />
</ProductList.Item>
))}
</div>
)}
</ProductList.Items>
<ProductList.Pagination />
</ProductList.Root>
</DataProvider>
);
}
import { DataProvider } from "@haus-storefront-react/core";
import { VendureProviderPreset } from "@haus-storefront-react/providers/vendure";
import { ProductList } from "@haus-storefront-react/product-list";
import { Search } from "@haus-storefront-react/search";
import { ScrollView, Text, TextInput, View } from "react-native";
const vendurePreset = new VendureProviderPreset({
apiUrl: "https://your-api.com/shop-api",
defaultChannelToken: "YOUR_TOKEN",
}).toPreset();
function Storefront() {
return (
<DataProvider platform='native' preset={vendurePreset}>
<ScrollView>
<Search.Root>
<Search.Input asChild>
<TextInput placeholder='Search products...' />
</Search.Input>
<Search.Results>
<Search.Products />
</Search.Results>
</Search.Root>
<ProductList.Root>
<ProductList.Items>
{({ items }) => (
<View>
{items.map((item) => (
<ProductList.Item key={item.id} product={item}>
<View>
<ProductList.Image />
<ProductList.Name />
<ProductList.Price />
<ProductList.AddToCartButton />
</View>
</ProductList.Item>
))}
</View>
)}
</ProductList.Items>
<ProductList.Pagination />
</ProductList.Root>
</ScrollView>
</DataProvider>
);
}
Example 3: Using Hooks Directly
- React
- React Native
import {
DataProvider,
useSdk,
useQuery,
useMutation,
} from "@haus-storefront-react/core";
import { useActiveOrder } from "@haus-storefront-react/hooks";
function CustomCart() {
const sdk = useSdk();
const { data: order, isLoading } = useActiveOrder();
const addItemMutation = useMutation({
mutationFn: (input) => sdk.addItemToOrder(input),
onSuccess: () => {
console.log("Item added to cart!");
},
});
const handleAddToCart = () => {
addItemMutation.mutate({
productVariantId: "123",
quantity: 1,
});
};
if (isLoading) return <div>Loading...</div>;
return (
<div>
<h2>Cart ({order?.lines.length || 0} items)</h2>
{order?.lines.map((line) => (
<div key={line.id}>
{line.productVariant.name} - {line.quantity} x {line.linePriceWithTax}
</div>
))}
<button onClick={handleAddToCart}>Add Item</button>
</div>
); }
import {
DataProvider,
useSdk,
useMutation,
} from "@haus-storefront-react/core";
import { useActiveOrder } from "@haus-storefront-react/hooks";
import { Button, Text, View } from "react-native";
function CustomCart() {
const sdk = useSdk();
const { data: order, isLoading } = useActiveOrder();
const addItemMutation = useMutation({
mutationFn: (input) => sdk.addItemToOrder(input),
onSuccess: () => {
console.log("Item added to cart!");
},
});
const handleAddToCart = () => {
addItemMutation.mutate({
productVariantId: "123",
quantity: 1,
});
};
if (isLoading) {
return (
<View>
<Text>Loading...</Text>
</View>
);
}
return (
<View>
<Text>Cart ({order?.lines.length || 0} items)</Text>
{order?.lines.map((line) => (
<Text key={line.id}>
{line.productVariant.name} - {line.quantity} x {line.linePriceWithTax}
</Text>
))}
<Button title='Add Item' onPress={handleAddToCart} />
</View>
);
}
Configuration Options
DataProvider Options
DataProvider accepts a preset (recommended for Vendure) or sdkInstance plus options:
- React
- React Native
import { DataProvider } from '@haus-storefront-react/core'
import { VendureProviderPreset } from '@haus-storefront-react/providers/vendure'
const preset = new VendureProviderPreset({
apiUrl: 'https://your-api.com/shop-api',
defaultChannelToken: 'YOUR_CHANNEL_TOKEN',
locale: 'en',
currency: 'USD',
persistOptions: {
enabled: true,
maxAge: 3600000, // 1 hour in milliseconds
},
}).toPreset()
<DataProvider
platform='web' // or "native" - auto-detected if not specified
preset={preset}
options={{
enabledFeatures: {
customPriceCurrency: (currencyCode) =>
currencyCode === 'SEK' ? 'kr' : currencyCode,
},
pluginConfigs: [
// Your plugin configs here
],
}}
>
<App />
</DataProvider>
import { SafeAreaView } from 'react-native'
import { DataProvider } from '@haus-storefront-react/core'
import { VendureProviderPreset } from '@haus-storefront-react/providers/vendure'
const preset = new VendureProviderPreset({
apiUrl: 'https://your-api.com/shop-api',
defaultChannelToken: 'YOUR_CHANNEL_TOKEN',
locale: 'en-US',
currency: 'USD',
persistOptions: {
enabled: true,
maxAge: 3600000,
},
}).toPreset()
<DataProvider platform='native' preset={preset}>
<SafeAreaView>
<App />
</SafeAreaView>
</DataProvider>
Note: The legacy
providerandvendureTokenprops were removed. UseVendureProviderPresetanddefaultChannelTokeninstead. Vendure interceptors are applied automatically by the preset.
Key Concepts
Headless Components
All components are "headless" - they provide logic and state management but allow complete UI customization. You control the styling and layout.
Compound Components
Many components use the compound component pattern for flexibility:
- React
- React Native
<Cart.Root>
<Cart.Items>
<Cart.Item orderLine={line}>
<Cart.Item.Image />
<Cart.Item.Price />
</Cart.Item>
</Cart.Items>
<Cart.Summary>{/* Summary content */}</Cart.Summary>
</Cart.Root>
import { Image, Text, View } from "react-native";
<Cart.Root>
<Cart.Items>
<Cart.Item orderLine={line}>
<Cart.Item.Image asChild>
<Image
source={{ uri: line.productVariant.featuredAsset?.preview }}
/>
</Cart.Item.Image>
<Cart.Item.Price />
</Cart.Item>
</Cart.Items>
<Cart.Summary>
<View>
<Text>Summary content</Text>
</View>
</Cart.Summary>
</Cart.Root>
asChild – Use Your Own Elements in Compound Components
Many components in the Haus Storefront libraries have an asChild prop. This allows you to replace the underlying HTML element (such as a <button>, <input>, or <form>) with your own component or element, without losing access to all the logic, props, and context.
This is particularly useful if you want to use your own styling, a UI library, or for example, a Next.js Link instead of a regular <a> tag.
Example: Replace Button with Your Own Component
- React
- React Native
import { Login } from "@haus-storefront-react/authentication";
<Login.Root>
<Login.Form asChild>
<form className='my-form'>
<Login.EmailInput asChild>
<input className='my-input' />
</Login.EmailInput>
<Login.PasswordInput asChild>
<input className='my-input' />
</Login.PasswordInput>
<Login.SubmitButton asChild>
<button className='my-button'>Log in</button>
</Login.SubmitButton>
</form>
</Login.Form>
</Login.Root>;
import { Login } from "@haus-storefront-react/authentication";
import { Pressable, Text, TextInput, View } from "react-native";
<Login.Root>
<Login.Form asChild>
<View>
<Login.EmailInput asChild>
<TextInput placeholder='Email' />
</Login.EmailInput>
<Login.PasswordInput asChild>
<TextInput placeholder='Password' secureTextEntry />
</Login.PasswordInput>
<Login.SubmitButton asChild>
<Pressable>
<Text>Log in</Text>
</Pressable>
</Login.SubmitButton>
</View>
</Login.Form>
</Login.Root>;
In the example above:
- All props and event handling from the Login components are passed through to your own elements.
- You can use any element (e.g.,
<button>or a custom React component) and still get the correct logic and accessibility.
Why Use asChild?
- Full control over markup and styling – You decide exactly which element is rendered.
- Keep all logic – You still get the correct props, events, and accessibility attributes.
- Integrate with UI libraries – Works with Chakra UI, Material UI, Tailwind, or any other UI library.
asChildis built on a "slot" pattern and works in all compound components where it's available.
Render Props
Components often use render props to provide context:
- React
- React Native
<ProductList.Root>
{({ products, isLoading, error }) => {
// Access context data
if (isLoading) return <div>Loading...</div>;
if (error) return <div>Error loading products</div>;
return <div>{/* Your UI */}</div>;
}}
</ProductList.Root>
import { Text, View } from "react-native";
<ProductList.Root>
{({ products, isLoading, error }) => {
if (isLoading) {
return (
<View>
<Text>Loading...</Text>
</View>
);
}
if (error) {
return (
<View>
<Text>Error loading products</Text>
</View>
);
}
return <View>{/* Your UI */}</View>;
}}
</ProductList.Root>
Platform Detection
Components work on both web (React) and mobile (React Native) platforms. The library automatically detects the platform and uses appropriate primitives. You can explicitly set the platform if needed:
- Web: Runs in browsers, uses
localStoragefor persistence - Native: Runs in React Native, uses
AsyncStoragefor persistence
- React
- React Native
import { DataProvider } from '@haus-storefront-react/core'
import { VendureProviderPreset } from '@haus-storefront-react/providers/vendure'
const preset = new VendureProviderPreset({
apiUrl: 'https://your-api.com/shop-api',
defaultChannelToken: 'YOUR_CHANNEL_TOKEN',
}).toPreset()
<DataProvider platform='web' preset={preset}>
<App />
</DataProvider>
import { SafeAreaView } from 'react-native'
import { DataProvider } from '@haus-storefront-react/core'
import { VendureProviderPreset } from '@haus-storefront-react/providers/vendure'
const preset = new VendureProviderPreset({
apiUrl: 'https://your-api.com/shop-api',
defaultChannelToken: 'YOUR_CHANNEL_TOKEN',
}).toPreset()
<DataProvider platform='native' preset={preset}>
<SafeAreaView>
<App />
</SafeAreaView>
</DataProvider>
TypeScript types on React Native
By default, all component props and hook signatures are typed for the web (DOM props like onClick, className, CSSProperties styles). In a React Native app, opt in to the native type variants by importing the native type augmentation once, at your app's entry point:
// App entry (e.g. App.tsx or app/_layout.tsx)
import '@haus-storefront-react/shared-types/native'
This switches every platform-dependent type in @haus-storefront-react/* to its native variant for the whole TypeScript program:
- Component slot props become React Native props (
ViewProps,TextProps,TextInputProps,onPress,StylePropstyles, etc.) - Hooks with platform-specific signatures (like
useElementSize,useInputChangeEventanduseWindowSizefrom@haus-storefront-react/common-hooks) show their native signatures
The import is type-only in effect (the module has no runtime side effects), and web apps are unaffected — without the import, all types stay web-flavored.
Next Steps
Now that you have the basics set up, explore the documentation:
- Learn about the Core Library: Understand the DataProvider, SDKs, and hooks
- Explore Storefront Components: Build your storefront with our pre-built components
- Use Storefront Hooks: Access e-commerce functionality with React hooks
- Customize Components: Use the ComponentProvider to customize component rendering
- Integrate with Vendure: Learn about plugin configurations and type-safe integrations
- Level up with Advanced Guides: Dive into
Advanced Playbookfor component composition, SDK extensions, and plugin strategies
Happy building!
Made with ❤️ by Haus Tech Team