> ## Documentation Index
> Fetch the complete documentation index at: https://docs.ollie.shop/llms.txt
> Use this file to discover all available pages before exploring further.

# Build Your First Component

> Step-by-step guide to creating a checkout component

## Overview

This guide walks through building a **free shipping progress bar** component. You'll learn how to:

* Create a component in your project
* Access checkout data with the SDK
* Run it locally
* Register and deploy it to your store

## Prerequisites

* The Ollie Shop CLI installed (`npm install -g @ollie-shop/cli`) — see [Installation](/ollie-shop/cli/installation)
* A scaffolded project (`npx create-ollie-shop`) — see the [Quickstart](/ollie-shop/get-started/quickstart)
* Basic React knowledge
* Access to a store

<Note>
  A component is a folder under `components/` in your project. It is **not** a
  standalone package — `ollieshop component create` only registers metadata in the
  database; it does not scaffold a folder. The actual code lives in your project.
</Note>

## Create the Component

<Steps>
  <Step title="Create the folder">
    Inside your project, create `components/FreeShippingBar/` with an `index.tsx`
    and a `meta.json`:

    ```
    your-project/
    ├── components/
    │   └── FreeShippingBar/
    │       ├── index.tsx          # Component code (entry point)
    │       ├── styles.module.css  # Styles (optional)
    │       └── meta.json          # Component metadata (id, slot, props)
    ├── package.json               # Project dependencies
    └── ...
    ```
  </Step>

  <Step title="Add meta.json">
    `meta.json` tells the tooling where the component goes. The `slot` is where it
    renders in the checkout (see [Slots](/ollie-shop/concepts/slots)). Leave `id`
    out for local development — an unlinked component gets a temporary `studio-*`
    id until you register it.

    ```json meta.json theme={"system"}
    {
      "name": "Free Shipping Bar",
      "slot": "your-slot"
    }
    ```
  </Step>
</Steps>

## Build the Component

Create `components/FreeShippingBar/index.tsx`:

```tsx index.tsx theme={"system"}
import React from 'react';
import { useCheckoutSession } from '@ollie-shop/sdk';
import styles from './styles.module.css';

export default function FreeShippingBar() {
  const { session } = useCheckoutSession();
  const { totals, locale } = session;

  const threshold = 10000; // $100 in cents
  const subtotal = totals.items;
  const remaining = threshold - subtotal;
  const progress = Math.min((subtotal / threshold) * 100, 100);

  const format = (cents: number) =>
    new Intl.NumberFormat(locale.language, {
      style: 'currency',
      currency: locale.currency,
    }).format(cents / 100);

  if (remaining <= 0) {
    return (
      <div className={`${styles.shippingBar} ${styles.success}`} role="status" aria-live="polite">
        <span>✓ Free shipping unlocked</span>
      </div>
    );
  }

  return (
    <div className={styles.shippingBar} role="status" aria-live="polite">
      <p>Add {format(remaining)} for free shipping</p>
      <div className={styles.progressTrack}>
        <div className={styles.progressFill} style={{ width: `${progress}%` }} />
      </div>
    </div>
  );
}
```

## Add Styles

Create `components/FreeShippingBar/styles.module.css`:

```css styles.module.css theme={"system"}
.shippingBar {
  background: #f3f4f6;
  padding: 12px;
  text-align: center;
  font-size: 14px;
}

.success {
  background: #10b981;
  color: white;
}

.progressTrack {
  background: #e5e7eb;
  height: 6px;
  border-radius: 3px;
  margin-top: 8px;
  overflow: hidden;
}

.progressFill {
  background: #3b82f6;
  height: 100%;
  transition: width 0.3s ease;
}
```

## Test Locally

Start the local dev server from your project root:

```bash theme={"system"}
npm start
# or, directly:
ollieshop start
```

This discovers your components, opens Ollie Studio in the browser, and hot-reloads
as you edit.

## Register & Deploy

<Steps>
  <Step title="Register the component">
    Create the component record in a store version to get its **component id**. You
    can do this in the admin dashboard, or via the CLI:

    ```bash theme={"system"}
    ollieshop component create \
      --version-id <version-uuid> \
      --name "Free Shipping Bar" \
      --slot your-slot
    ```
  </Step>

  <Step title="Deploy">
    Deploy bundles the local `components/FreeShippingBar` folder and uploads it to
    the builder. Pass the component id from the previous step and the folder name:

    ```bash theme={"system"}
    ollieshop deploy \
      --component-id <component-uuid> \
      --name FreeShippingBar \
      --wait
    ```

    `--wait` polls until the build finishes.
  </Step>

  <Step title="Verify">
    ```bash theme={"system"}
    ollieshop component list --store-id <store-uuid>
    ```
  </Step>
</Steps>

## Using Checkout Data

The SDK exposes hooks for reading checkout data. `useCheckoutSession` returns the
parsed session:

```tsx theme={"system"}
const { session } = useCheckoutSession();

session.cartItems        // Array of cart items
session.totals.items     // Subtotal of items, in cents
session.totals.shipping  // Shipping cost, in cents (optional)
session.totals.total     // Grand total, in cents
session.locale           // { currency, language, country }
session.customer         // Customer data (optional)
```

See the [hooks reference](/ollie-shop/api/hooks) and the
[`CheckoutSession` type](/ollie-shop/api/CheckoutSession) for the full shape.

## Adding Interactivity

Cart changes go through [`useCheckoutAction`](/ollie-shop/api/useCheckoutAction),
not `useCheckoutSession`:

```tsx theme={"system"}
import { useCheckoutAction } from '@ollie-shop/sdk';

function RemoveFirstItemButton() {
  const { execute, isPending } = useCheckoutAction('REMOVE_ITEMS', {
    onError: ({ serverError }) =>
      console.error('Failed to remove item', serverError?.message),
  });

  return (
    <button onClick={() => execute([0])} disabled={isPending}>
      Remove first item
    </button>
  );
}
```

## Best Practices

### Performance

```tsx theme={"system"}
// Memoize expensive calculations derived from the session
const totalSavings = useMemo(
  () =>
    session.cartItems.reduce(
      (sum, item) => sum + (item.originalPrice - item.price) * item.quantity,
      0,
    ),
  [session.cartItems],
);
```

### Error Handling

Surface failures to the user via [`useMessages`](/ollie-shop/api/useMessages):

```tsx theme={"system"}
const { addMessage } = useMessages();
const { execute } = useCheckoutAction('ADD_ITEMS', {
  onError: ({ serverError }) => addMessage({ type: 'error', error: serverError }),
});
```

### Accessibility

```tsx theme={"system"}
<div role="status" aria-live="polite">
  {remaining > 0
    ? `${format(remaining)} until free shipping`
    : 'Free shipping unlocked'}
</div>
```

## Next Steps

<CardGroup cols={2}>
  <Card title="Custom Component 101" icon="star" href="/ollie-shop/customization/custom-component">
    A fully custom CartItem walkthrough
  </Card>

  <Card title="API Reference" icon="book" href="/ollie-shop/api/hooks">
    Complete SDK documentation
  </Card>
</CardGroup>
