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

# Calendar overview

> Rent items between two dates, at a price you set for each rental period

export const CalendarRentalPdp = () => {
  const PERIODS = [{
    days: 4,
    price: 63.99
  }, {
    days: 7,
    price: 83.99
  }, {
    days: 14,
    price: 117.99
  }, {
    days: 30,
    price: 161.99
  }];
  const ADDONS = [{
    id: "damage",
    title: "Damage protection",
    desc: "Covers accidental damage during your rental",
    price: 9
  }, {
    id: "cleaning",
    title: "Cleaning",
    desc: "",
    price: 12,
    required: true
  }];
  const START = new Date(2025, 0, 10);
  const [period, setPeriod] = useState(0);
  const [addons, setAddons] = useState({
    damage: false,
    cleaning: true
  });
  const money = n => "$" + n.toFixed(2);
  const fmt = d => d.getDate() + " " + d.toLocaleString("en", {
    month: "short"
  }) + " " + String(d.getFullYear()).slice(2);
  const endDate = new Date(START);
  endDate.setDate(START.getDate() + PERIODS[period].days);
  const perDay = PERIODS[period].price / PERIODS[period].days;
  const addonTotal = ADDONS.reduce((s, a) => s + (addons[a.id] ? a.price : 0), 0);
  const total = PERIODS[period].price + addonTotal;
  const toggleAddon = a => {
    if (a.required) return;
    setAddons({
      ...addons,
      [a.id]: !addons[a.id]
    });
  };
  const t = {
    border: "#cccccc",
    text: "#000000",
    subdued: "#656565",
    accent: "#000000",
    radius: 8
  };
  const chip = selected => ({
    border: "1px solid " + (selected ? t.accent : t.border),
    background: selected ? t.accent : "#ffffff",
    color: selected ? "#ffffff" : t.text,
    borderRadius: t.radius,
    padding: 6,
    minHeight: 28,
    display: "flex",
    alignItems: "center",
    lineHeight: 1,
    fontSize: 11,
    cursor: "pointer",
    transition: "background-color 150ms ease, border-color 150ms ease, color 150ms ease"
  });
  const label = {
    display: "block",
    fontSize: 11,
    color: t.subdued,
    marginBottom: 3
  };
  const line = {
    display: "flex",
    justifyContent: "space-between",
    fontSize: 11
  };
  const windowRef = useRef(null);
  const [zoom, setZoom] = useState(0.4);
  useEffect(() => {
    const el = windowRef.current;
    if (!el || typeof ResizeObserver === "undefined") return undefined;
    const fit = () => setZoom(Math.max(0.2, Math.min(1, el.clientWidth / 853)));
    fit();
    const ro = new ResizeObserver(fit);
    ro.observe(el);
    return () => ro.disconnect();
  }, []);
  return <div className="not-prose sc-cpdp" aria-label="Example calendar rental product page">
      <style>{`
        .sc-cpdp { width: 100%; min-width: 0; font-family: "Inter", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif; font-size: 11px; color: #000; }
        .sc-cpdp button { font: inherit; margin: 0; appearance: none; text-align: left; }
        .sc-cpdp button:focus-visible { outline: 2px solid #000; outline-offset: 2px; }
        .sc-cpdp-window { width: 100%; min-width: 0; max-width: 640px; height: auto; max-height: 520px; margin: 0 auto; display: flex; flex-direction: column; touch-action: pan-y;
          background: linear-gradient(135deg, #ffffff 45%, #f7f6f2 100%); border-radius: 10px; overflow: hidden;
          box-shadow: 0 0 0 1px rgba(11,13,24,.06), 0 1px 2px rgba(11,13,24,.024), 0 3px 6px rgba(11,13,24,.03), 0 8px 14px rgba(11,13,24,.035), 0 16px 28px rgba(11,13,24,.04); }
        .sc-cpdp-titlebar { position: relative; flex: none; display: flex; align-items: center; gap: 6px; padding: 9px 12px; border-bottom: 1px solid #ebebeb; }
        .sc-cpdp-dot { width: 9px; height: 9px; border-radius: 50%; background: #e3e3e3; }
        .sc-cpdp-url { margin: 0 auto; font-size: 11px; color: #85817a; background: #f3f2ee; border-radius: 6px; padding: 3px 10px; line-height: 1.2; pointer-events: none; }
        /* The page: everything below the title bar. Desktop keeps an inner
           scrollbar; touch devices pass the swipe to the docs page. */
        .sc-cpdp-page { flex: 1 1 auto; min-height: 0; overflow-y: auto; overscroll-behavior: contain; background: #fff; scrollbar-width: thin; scrollbar-color: #d6d3cc transparent; }
        .sc-cpdp-page::-webkit-scrollbar { width: 6px; }
        .sc-cpdp-page::-webkit-scrollbar-thumb { background: #d6d3cc; border-radius: 3px; }
        @media (pointer: coarse) { .sc-cpdp-page { overflow: hidden; overscroll-behavior: auto; } }
        .sc-cpdp-store { display: grid; grid-template-columns: 1fr auto 1fr; align-items: center; padding: 12px 16px; font-size: 11px; }
        .sc-cpdp-store-name { font-family: "Instrument Serif", Georgia, "Times New Roman", serif; font-size: 15px; letter-spacing: -0.01em; color: #202603; }
        .sc-cpdp-store-nav { display: flex; gap: 12px; color: #333; }
        .sc-cpdp-store-icons { display: flex; justify-content: flex-end; align-items: center; gap: 12px; color: #333; }
        .sc-cpdp-store-icons svg { width: 14px; height: 14px; }
        .sc-cpdp-avatar { width: 16px; height: 16px; border-radius: 50%; background: #2f6bff; color: #fff; font-size: 8px; font-weight: 600; display: grid; place-items: center; }
        .sc-cpdp-body { display: grid; grid-template-columns: minmax(0, 1fr) minmax(0, 1fr); gap: 20px; align-items: start; padding: 4px 16px 16px; }
        .sc-cpdp-media { position: sticky; top: 0; }
        .sc-cpdp-product { display: block; }
        .sc-cpdp-placeholder { aspect-ratio: 4 / 5; border-radius: 4px; background: #f3f2ee; color: #c9c5bc; display: grid; place-items: center; }
        .sc-cpdp-placeholder svg { width: 40px; height: 40px; }
        .sc-cpdp-panel { display: flex; flex-direction: column; gap: 12px; padding: 0; }
        .sc-cpdp-desc { padding: 16px; display: grid; gap: 12px; font-size: 11px; line-height: 1.5; color: #333; border-top: 1px solid #ebebeb; }
        .sc-cpdp-desc h4 { margin: 0 0 4px; font-size: 11px; font-weight: 600; color: #000; }
        .sc-cpdp-desc p { margin: 0; }
        .sc-cpdp-desc ul { margin: 0; padding-left: 16px; list-style: disc; }
        .sc-cpdp .sc-cpdp-cta { background: #1a1a1a; color: #fff; border: 0; border-radius: 8px; text-align: center; font-size: 12px; font-weight: 550; padding: 10px 0; width: 100%; cursor: pointer; }
        .sc-cpdp .sc-cpdp-cta:hover { background: #303030; }
      `}</style>

      <div className="sc-cpdp-window" ref={windowRef}>
        <div className="sc-cpdp-titlebar">
          <span className="sc-cpdp-dot" />
          <span className="sc-cpdp-dot" />
          <span className="sc-cpdp-dot" />
          <span className="sc-cpdp-url">yourstore.com/products/all-mountain-ski-set</span>
        </div>

        <div className="sc-cpdp-page">
        <div className="sc-cpdp-zoom" style={{
    zoom,
    width: 853
  }}>
          <div className="sc-cpdp-store" aria-hidden="true">
            <span className="sc-cpdp-store-nav">
              <span>Methods</span>
              <span>Collections</span>
              <span>Plans</span>
            </span>
            <span className="sc-cpdp-store-name">Summit store</span>
            <span className="sc-cpdp-store-icons">
              <svg viewBox="0 0 16 16" fill="none"><circle cx="7" cy="7" r="4.5" stroke="currentColor" strokeWidth="1.4" /><path d="M10.5 10.5L14 14" stroke="currentColor" strokeWidth="1.4" strokeLinecap="round" /></svg>
              <span className="sc-cpdp-avatar">M</span>
              <svg viewBox="0 0 16 16" fill="none"><path d="M3 5.5h10l-.8 8H3.8L3 5.5z" stroke="currentColor" strokeWidth="1.4" strokeLinejoin="round" /><path d="M5.5 5.5V4.5a2.5 2.5 0 015 0v1" stroke="currentColor" strokeWidth="1.4" /></svg>
            </span>
          </div>
        <div className="sc-cpdp-body">
          {}
          <div className="sc-cpdp-media" aria-hidden="true">
            <div className="sc-cpdp-placeholder">
              <svg viewBox="0 0 48 48" fill="none" xmlns="http://www.w3.org/2000/svg">
                <rect x="6" y="10" width="36" height="28" rx="3" stroke="currentColor" strokeWidth="2" />
                <circle cx="17" cy="20" r="3.5" stroke="currentColor" strokeWidth="2" />
                <path d="M8 34l10-9 7 6 6-5 9 8" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" />
              </svg>
            </div>
          </div>

          {}
          <div className="sc-cpdp-panel">
            <div className="sc-cpdp-product">
              <div style={{
    fontSize: 16,
    fontWeight: 550,
    lineHeight: 1.2
  }}>All-mountain ski set</div>
              <div style={{
    fontSize: 12,
    color: t.subdued,
    marginTop: 4
  }}>From $5.40 per day</div>
            </div>


            <div>
              <div style={{
    border: "1px solid " + t.accent,
    outline: "1px solid " + t.accent,
    borderRadius: t.radius,
    padding: 12,
    background: "#ffffff"
  }}>
                <div style={{
    display: "flex",
    alignItems: "flex-start",
    gap: 6,
    lineHeight: 1
  }}>
                  <span style={{
    position: "relative",
    width: 14,
    height: 14,
    flexShrink: 0,
    borderRadius: "50%",
    boxShadow: "inset 0 0 0 1px " + t.accent
  }}>
                    <span style={{
    position: "absolute",
    inset: 3,
    borderRadius: "50%",
    background: t.accent
  }} />
                  </span>
                  <span style={{
    flex: 1,
    minWidth: 0,
    display: "flex",
    flexDirection: "column",
    gap: 4
  }}>
                    <span style={{
    display: "flex",
    justifyContent: "space-between",
    gap: 6
  }}>
                      <span style={{
    fontSize: 12
  }}>Rent</span>
                      <span style={{
    fontSize: 12,
    marginLeft: "auto"
  }}>From $5.40 per day</span>
                    </span>
                    <span style={{
    fontSize: 10,
    color: t.subdued,
    textAlign: "right"
  }}>Yours for 4 to 30 days</span>
                  </span>
                </div>

                <div style={{
    marginTop: 12,
    borderTop: "1px solid " + t.border,
    paddingTop: 12,
    display: "grid",
    gap: 12
  }}>
                  {}
                  <div>
                    <span style={label}>Rental period</span>
                    <div style={{
    display: "grid",
    gridTemplateColumns: "repeat(3, minmax(0, 1fr))",
    gap: 6
  }}>
                      {PERIODS.map((p, i) => {
    const on = i === period;
    return <button key={p.days} type="button" aria-pressed={on} onClick={() => setPeriod(i)} style={{
      ...chip(on),
      flexDirection: "column",
      alignItems: "center",
      justifyContent: "center",
      gap: 4,
      minHeight: 34,
      textAlign: "center"
    }}>
                            <span style={{
      fontSize: 11
    }}>{p.days} days</span>
                            <span style={{
      fontSize: 10,
      color: on ? "#ffffff" : t.subdued
    }}>{money(p.price)}</span>
                          </button>;
  })}
                    </div>
                  </div>

                  {}
                  <div style={{
    border: "1px solid " + t.border,
    borderRadius: t.radius,
    display: "flex",
    overflow: "hidden"
  }}>
                    <div style={{
    flex: 1,
    padding: 6,
    display: "flex",
    flexDirection: "column",
    gap: 3,
    lineHeight: 1
  }}>
                      <span style={{
    fontSize: 9
  }}>Arrive by</span>
                      <span style={{
    fontSize: 11,
    whiteSpace: "nowrap"
  }}>{fmt(START)}</span>
                    </div>
                    <div style={{
    flex: 1,
    padding: 6,
    display: "flex",
    flexDirection: "column",
    gap: 3,
    lineHeight: 1,
    borderLeft: "1px solid " + t.border
  }}>
                      <span style={{
    fontSize: 9
  }}>Return by</span>
                      <span style={{
    fontSize: 11,
    whiteSpace: "nowrap"
  }}>{fmt(endDate)}</span>
                    </div>
                  </div>

                  {}
                  <div>
                    <span style={label}>Add-ons</span>
                    <div style={{
    border: "1px solid " + t.border,
    borderRadius: t.radius,
    background: "#ffffff",
    overflow: "hidden",
    display: "flex",
    flexDirection: "column"
  }}>
                      {ADDONS.map((a, i) => {
    const on = !!addons[a.id];
    return <button key={a.id} type="button" aria-pressed={on} aria-disabled={a.required || undefined} onClick={() => toggleAddon(a)} style={{
      display: "flex",
      alignItems: "center",
      gap: 6,
      padding: 6,
      lineHeight: 1,
      background: "transparent",
      border: 0,
      borderTop: i ? "1px solid " + t.border : 0,
      cursor: a.required ? "default" : "pointer",
      width: "100%",
      color: t.text
    }}>
                            <span style={{
      flex: "none",
      width: 16,
      height: 16,
      borderRadius: "50%",
      border: "1px solid " + (on ? a.required ? t.subdued : t.accent : t.border),
      background: on ? a.required ? t.subdued : t.accent : "transparent",
      display: "grid",
      placeItems: "center",
      color: "#ffffff",
      transition: "background-color 150ms ease, border-color 150ms ease"
    }}>
                              <svg viewBox="0 0 10 10" fill="none" width="10" height="10" style={{
      opacity: on ? 1 : 0
    }}>
                                <path d="M1.5 5.5l2.3 2.3L8.5 2.5" stroke="currentColor" strokeWidth="1.6" strokeLinecap="round" strokeLinejoin="round" />
                              </svg>
                            </span>
                            <span style={{
      flex: 1,
      minWidth: 0,
      display: "flex",
      flexDirection: "column",
      gap: 4
    }}>
                              <span style={{
      display: "inline-flex",
      alignItems: "center",
      gap: 6,
      fontSize: 11
    }}>
                                {a.title}
                                {a.required && <span style={{
      fontSize: 9,
      textTransform: "uppercase",
      letterSpacing: "0.04em",
      color: t.subdued
    }}>
                                    Required
                                  </span>}
                              </span>
                              {a.desc && <span style={{
      fontSize: 10,
      color: t.subdued
    }}>{a.desc}</span>}
                            </span>
                            <span style={{
      flex: "none",
      whiteSpace: "nowrap",
      fontSize: 11
    }}>{money(a.price)}</span>
                          </button>;
  })}
                    </div>
                  </div>

                  {}
                  <div>
                    <div style={{
    borderTop: "1px solid " + t.border,
    paddingTop: 6,
    display: "flex",
    flexDirection: "column",
    gap: 5
  }}>
                      <span style={line}>
                        <span>
                          {money(perDay)}/day x {PERIODS[period].days} days
                        </span>
                        <span>{money(PERIODS[period].price)}</span>
                      </span>
                      {ADDONS.filter(a => addons[a.id]).map(a => <span key={a.id} style={line}>
                          <span>{a.title}</span>
                          <span>{money(a.price)}</span>
                        </span>)}
                    </div>
                    <div style={{
    borderTop: "1px solid " + t.border,
    marginTop: 6,
    paddingTop: 6
  }}>
                      <span style={{
    ...line,
    fontWeight: 550
  }}>
                        <span>Total</span>
                        <span>{money(total)}</span>
                      </span>
                    </div>
                  </div>
                </div>
              </div>
            </div>

            <button type="button" className="sc-cpdp-cta">
              Add to cart
            </button>
          </div>
        </div>

          <div className="sc-cpdp-desc" aria-hidden="true">
            <div>
              <h4>Details</h4>
              <p>
                A forgiving all-mountain ski with a poplar core and a 92mm waist, paired with adjustable poles.
                Sized to your height and weight at checkout.
              </p>
            </div>
            <div>
              <h4>What's included</h4>
              <ul>
                <li>Skis with bindings, tuned and waxed before every rental</li>
                <li>Adjustable poles</li>
                <li>Padded ski bag for transport</li>
              </ul>
            </div>
            <div>
              <h4>How rental works</h4>
              <p>
                Your set arrives by the first day of your rental period. Return it by the last day using the
                prepaid label in the bag, or drop it at any of our stores.
              </p>
            </div>
          </div>
        </div>
        </div>
      </div>
    </div>;
};

Calendar rental, also known as one-off or event rental, is the most recognizable method: you might have rented skis, a suit for a wedding, or a dress for an event this way.

<Frame type="glass" caption="Pick a rental period or an add-on and the dates and total follow.">
  <CalendarRentalPdp />
</Frame>

***

## How it works

The customer books an item between two dates, which Supercycle calls the **Arrive by** and **Return by** dates. You set the rental periods, the number of days a product can be rented for, and a price for each. The customer picks a period and a start date, and the return date follows.

The customer pays the full rental price at checkout. Fixed fees such as cleaning or insurance are added on top of the period price and shown as their own lines, so the customer sees what makes up the total. Logistics buffers keep the item off the calendar while it's prepared, shipped, and restocked, so two rentals can't be booked too close together.

<Info>
  To charge a customer later for damage or an extension, turn on [card vaulting](/documentation/configuration/risk/card-vaulting) first.
</Info>

***

## Configurations

Set up calendar rental on a product, then add the extras your business needs.

<CardGroup cols={2}>
  <Card title="Product setup" icon="sliders-simple" href="/documentation/methods/calendar/product-setup">
    Turn on calendar rental for a product and add its rental periods and prices.
  </Card>

  <Card title="Fixed fees" icon="receipt" href="/documentation/methods/calendar/fixed-fees">
    Charge set fees on top of the rental period, such as a cleaning fee.
  </Card>

  <Card title="Cycle extensions" icon="calendar-plus" href="/documentation/manage/cycles/extensions">
    Let customers extend an active cycle from their account and pay the difference.
  </Card>

  <Card title="Logistics buffers" icon="warehouse" href="/documentation/configuration/logistics/buffers">
    Add preparation, delivery, and restock time around rentals.
  </Card>

  <Card title="Filter by availability" icon="filter" href="/documentation/online-store/search">
    Let customers filter a collection by the dates they need.
  </Card>
</CardGroup>
