Customer Account Pages Intermediate 10 min read

Order Details Page

Build a comprehensive order details page showing line items, shipping information, fulfillment status, and order history.

The order details page gives customers full visibility into their purchase. A well-designed order page reduces support requests and builds trust.

Order Template Structure

{# templates/customers/order.liquid #}
{% section 'customer-order' %}

Order Details Section

{# sections/customer-order.liquid #}
<section class="customer-order section-{{ section.id }}">
<div class="container">
<header class="customer-order__header">
<div class="customer-order__title-group">
<a href="{{ routes.account_url }}" class="customer-order__back">
← Back to Account
</a>
<h1 class="customer-order__heading">Order {{ order.name }}</h1>
<p class="customer-order__date">
Placed on {{ order.created_at | date: '%B %d, %Y at %I:%M %p' }}
</p>
</div>
<div class="customer-order__status">
<span class="order-badge order-badge--{{ order.financial_status }}">
{{ order.financial_status_label }}
</span>
{%- if order.fulfillment_status -%}
<span class="order-badge order-badge--{{ order.fulfillment_status }}">
{{ order.fulfillment_status_label }}
</span>
{%- endif -%}
</div>
</header>
{# Cancelled order notice #}
{%- if order.cancelled -%}
<div class="customer-order__cancelled" role="alert">
<h2>Order Cancelled</h2>
<p>
This order was cancelled on {{ order.cancelled_at | date: '%B %d, %Y' }}
{%- if order.cancel_reason != blank -%}
. Reason: {{ order.cancel_reason }}
{%- endif -%}
</p>
</div>
{%- endif -%}
<div class="customer-order__grid">
{# Line items #}
<div class="customer-order__items">
<h2>Items</h2>
<table class="order-items-table">
<thead>
<tr>
<th colspan="2">Product</th>
<th>Price</th>
<th>Qty</th>
<th>Total</th>
</tr>
</thead>
<tbody>
{%- for line_item in order.line_items -%}
<tr>
<td class="order-items-table__image">
{%- if line_item.image -%}
<a href="{{ line_item.product.url }}">
{{ line_item.image | image_url: width: 100 | image_tag:
loading: 'lazy'
}}
</a>
{%- endif -%}
</td>
<td class="order-items-table__details">
<a href="{{ line_item.product.url }}" class="order-items-table__title">
{{ line_item.title }}
</a>
{%- if line_item.variant.title != 'Default Title' -%}
<p class="order-items-table__variant">
{{ line_item.variant.title }}
</p>
{%- endif -%}
{%- if line_item.sku != blank -%}
<p class="order-items-table__sku">
SKU: {{ line_item.sku }}
</p>
{%- endif -%}
{# Line item properties #}
{%- if line_item.properties.size > 0 -%}
<ul class="order-items-table__properties">
{%- for property in line_item.properties -%}
{%- unless property.last == blank -%}
<li>
<strong>{{ property.first }}:</strong>
{%- if property.last contains '/uploads/' -%}
<a href="{{ property.last }}">View file</a>
{%- else -%}
{{ property.last }}
{%- endif -%}
</li>
{%- endunless -%}
{%- endfor -%}
</ul>
{%- endif -%}
{# Fulfillment status for this item #}
{%- if line_item.fulfillment -%}
<p class="order-items-table__fulfillment">
<span class="badge badge--fulfilled">Fulfilled</span>
</p>
{%- endif -%}
</td>
<td class="order-items-table__price">
{%- if line_item.original_price != line_item.final_price -%}
<del>{{ line_item.original_price | money }}</del>
{{ line_item.final_price | money }}
{%- else -%}
{{ line_item.original_price | money }}
{%- endif -%}
</td>
<td class="order-items-table__quantity">
{{ line_item.quantity }}
</td>
<td class="order-items-table__total">
{{ line_item.final_line_price | money }}
</td>
</tr>
{%- endfor -%}
</tbody>
</table>
</div>
{# Order summary #}
<div class="customer-order__summary">
<h2>Order Summary</h2>
<div class="order-summary">
<div class="order-summary__row">
<span>Subtotal</span>
<span>{{ order.line_items_subtotal_price | money }}</span>
</div>
{%- if order.total_discounts > 0 -%}
<div class="order-summary__row order-summary__row--discount">
<span>
Discount
{%- for discount in order.discount_applications -%}
({{ discount.title }})
{%- endfor -%}
</span>
<span>-{{ order.total_discounts | money }}</span>
</div>
{%- endif -%}
{%- for shipping_method in order.shipping_methods -%}
<div class="order-summary__row">
<span>Shipping ({{ shipping_method.title }})</span>
<span>{{ shipping_method.price | money }}</span>
</div>
{%- endfor -%}
{%- if order.tax_lines.size > 0 -%}
{%- for tax_line in order.tax_lines -%}
<div class="order-summary__row">
<span>{{ tax_line.title }} ({{ tax_line.rate_percentage }}%)</span>
<span>{{ tax_line.price | money }}</span>
</div>
{%- endfor -%}
{%- endif -%}
<div class="order-summary__row order-summary__row--total">
<span>Total</span>
<span>{{ order.total_price | money }} {{ order.currency }}</span>
</div>
</div>
</div>
</div>
{# Addresses #}
<div class="customer-order__addresses">
<div class="customer-order__address">
<h3>Shipping Address</h3>
<address>
{{ order.shipping_address | format_address }}
</address>
</div>
<div class="customer-order__address">
<h3>Billing Address</h3>
<address>
{{ order.billing_address | format_address }}
</address>
</div>
</div>
{# Fulfillments #}
{%- if order.fulfillments.size > 0 -%}
<div class="customer-order__fulfillments">
<h2>Shipments</h2>
{%- for fulfillment in order.fulfillments -%}
<div class="fulfillment-card">
<header class="fulfillment-card__header">
<h3>Shipment {{ forloop.index }}</h3>
<p class="fulfillment-card__date">
Shipped {{ fulfillment.created_at | date: '%B %d, %Y' }}
</p>
</header>
{# Tracking info #}
{%- if fulfillment.tracking_number -%}
<div class="fulfillment-card__tracking">
<p>
<strong>{{ fulfillment.tracking_company }}</strong>
</p>
{%- if fulfillment.tracking_url -%}
<a href="{{ fulfillment.tracking_url }}" target="_blank" rel="noopener" class="button button--secondary button--small">
Track Package: {{ fulfillment.tracking_number }}
</a>
{%- else -%}
<p>Tracking: {{ fulfillment.tracking_number }}</p>
{%- endif -%}
</div>
{%- endif -%}
{# Items in this fulfillment #}
<div class="fulfillment-card__items">
{%- for line_item in fulfillment.fulfillment_line_items -%}
<div class="fulfillment-card__item">
{%- if line_item.line_item.image -%}
{{ line_item.line_item.image | image_url: width: 60 | image_tag }}
{%- endif -%}
<span>
{{ line_item.line_item.title }} × {{ line_item.quantity }}
</span>
</div>
{%- endfor -%}
</div>
</div>
{%- endfor -%}
</div>
{%- endif -%}
{# Refunds #}
{%- if order.refunds.size > 0 -%}
<div class="customer-order__refunds">
<h2>Refunds</h2>
{%- for refund in order.refunds -%}
<div class="refund-card">
<p class="refund-card__date">
Refunded on {{ refund.created_at | date: '%B %d, %Y' }}
</p>
<p class="refund-card__amount">
Amount: {{ refund.amount | money }}
</p>
{%- if refund.note != blank -%}
<p class="refund-card__note">Note: {{ refund.note }}</p>
{%- endif -%}
</div>
{%- endfor -%}
</div>
{%- endif -%}
{# Order note #}
{%- if order.note != blank -%}
<div class="customer-order__note">
<h2>Order Note</h2>
<p>{{ order.note }}</p>
</div>
{%- endif -%}
</div>
</section>
{% schema %}
{
"name": "Customer Order",
"settings": []
}
{% endschema %}

Line Item Object

Each line item in an order has extensive data:

{{ line_item.title }} {# Product title #}
{{ line_item.variant.title }} {# Variant title #}
{{ line_item.sku }} {# SKU #}
{{ line_item.quantity }} {# Quantity ordered #}
{{ line_item.grams }} {# Weight in grams #}
{# Pricing #}
{{ line_item.original_price }} {# Original unit price #}
{{ line_item.final_price }} {# Price after discounts #}
{{ line_item.original_line_price }} {# Original total #}
{{ line_item.final_line_price }} {# Final total #}
{# Product references #}
{{ line_item.product }} {# Product object #}
{{ line_item.product.url }} {# Product URL #}
{{ line_item.variant }} {# Variant object #}
{{ line_item.image }} {# Line item image #}
{# Fulfillment #}
{{ line_item.fulfillment }} {# Fulfillment object if shipped #}
{{ line_item.fulfillment_status }} {# fulfilled, partial, null #}
{# Custom properties #}
{{ line_item.properties }} {# Array of custom properties #}
{{ line_item.selling_plan_allocation }} {# Subscription info #}

Fulfillment Object

{{ fulfillment.created_at }} {# Ship date #}
{{ fulfillment.tracking_company }} {# Carrier name #}
{{ fulfillment.tracking_number }} {# Tracking number #}
{{ fulfillment.tracking_url }} {# Tracking URL #}
{{ fulfillment.fulfillment_line_items }} {# Items in shipment #}

Mobile-Friendly Line Items

For mobile, transform the table into cards:

{# Mobile-friendly order items #}
<div class="order-items">
{%- for line_item in order.line_items -%}
<div class="order-item">
<div class="order-item__image">
{%- if line_item.image -%}
{{ line_item.image | image_url: width: 80 | image_tag }}
{%- endif -%}
</div>
<div class="order-item__details">
<h3 class="order-item__title">{{ line_item.title }}</h3>
{%- if line_item.variant.title != 'Default Title' -%}
<p class="order-item__variant">{{ line_item.variant.title }}</p>
{%- endif -%}
<p class="order-item__price">
{{ line_item.final_price | money }} × {{ line_item.quantity }}
</p>
<p class="order-item__total">
<strong>{{ line_item.final_line_price | money }}</strong>
</p>
</div>
</div>
{%- endfor -%}
</div>

Order Styles

.customer-order {
padding: var(--spacing-2xl) 0;
}
.customer-order__header {
display: flex;
justify-content: space-between;
align-items: flex-start;
flex-wrap: wrap;
gap: var(--spacing-md);
margin-bottom: var(--spacing-xl);
}
.customer-order__back {
display: block;
font-size: 0.875rem;
color: var(--color-text-light);
margin-bottom: var(--spacing-sm);
}
.customer-order__heading {
font-size: clamp(1.5rem, 4vw, 2rem);
margin: 0;
}
.customer-order__date {
color: var(--color-text-light);
font-size: 0.875rem;
}
.customer-order__status {
display: flex;
gap: var(--spacing-sm);
}
.order-badge {
display: inline-block;
padding: 4px 12px;
border-radius: 9999px;
font-size: 0.75rem;
font-weight: 500;
text-transform: uppercase;
}
.order-badge--paid {
background: #d1fae5;
color: #065f46;
}
.order-badge--pending {
background: #fef3c7;
color: #92400e;
}
.order-badge--refunded {
background: #e0e7ff;
color: #3730a3;
}
.order-badge--fulfilled {
background: #d1fae5;
color: #065f46;
}
.order-badge--unfulfilled {
background: #fef3c7;
color: #92400e;
}
.order-badge--partial {
background: #dbeafe;
color: #1e40af;
}
.customer-order__cancelled {
background: #fef2f2;
border: 1px solid #fecaca;
padding: var(--spacing-md);
border-radius: var(--border-radius);
margin-bottom: var(--spacing-xl);
}
.customer-order__cancelled h2 {
color: #b91c1c;
margin: 0 0 var(--spacing-sm);
}
.customer-order__grid {
display: grid;
gap: var(--spacing-xl);
}
@media (min-width: 768px) {
.customer-order__grid {
grid-template-columns: 1fr 300px;
}
}
/* Items table */
.order-items-table {
width: 100%;
border-collapse: collapse;
}
.order-items-table th,
.order-items-table td {
padding: var(--spacing-sm);
text-align: left;
border-bottom: 1px solid var(--color-border);
vertical-align: top;
}
.order-items-table th {
font-size: 0.75rem;
text-transform: uppercase;
letter-spacing: 0.05em;
color: var(--color-text-light);
}
.order-items-table__image img {
width: 80px;
height: 80px;
object-fit: cover;
border-radius: var(--border-radius-sm);
}
.order-items-table__title {
font-weight: 500;
text-decoration: none;
color: inherit;
}
.order-items-table__variant,
.order-items-table__sku {
font-size: 0.875rem;
color: var(--color-text-light);
margin: var(--spacing-xs) 0 0;
}
.order-items-table__properties {
font-size: 0.875rem;
list-style: none;
padding: 0;
margin: var(--spacing-sm) 0 0;
}
/* Order summary */
.order-summary {
background: var(--color-background-subtle);
padding: var(--spacing-md);
border-radius: var(--border-radius);
}
.order-summary__row {
display: flex;
justify-content: space-between;
padding: var(--spacing-sm) 0;
border-bottom: 1px solid var(--color-border);
}
.order-summary__row:last-child {
border-bottom: none;
}
.order-summary__row--discount {
color: #059669;
}
.order-summary__row--total {
font-weight: 600;
font-size: 1.125rem;
padding-top: var(--spacing-md);
margin-top: var(--spacing-sm);
border-top: 2px solid var(--color-border);
}
/* Addresses */
.customer-order__addresses {
display: grid;
gap: var(--spacing-lg);
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
margin-top: var(--spacing-xl);
padding-top: var(--spacing-xl);
border-top: 1px solid var(--color-border);
}
.customer-order__address h3 {
font-size: 0.875rem;
text-transform: uppercase;
letter-spacing: 0.05em;
margin-bottom: var(--spacing-sm);
}
.customer-order__address address {
font-style: normal;
color: var(--color-text-light);
line-height: 1.6;
}
/* Fulfillments */
.customer-order__fulfillments {
margin-top: var(--spacing-xl);
}
.fulfillment-card {
border: 1px solid var(--color-border);
border-radius: var(--border-radius);
padding: var(--spacing-md);
margin-bottom: var(--spacing-md);
}
.fulfillment-card__header {
margin-bottom: var(--spacing-md);
}
.fulfillment-card__header h3 {
margin: 0;
}
.fulfillment-card__date {
font-size: 0.875rem;
color: var(--color-text-light);
}
.fulfillment-card__items {
display: flex;
flex-wrap: wrap;
gap: var(--spacing-sm);
margin-top: var(--spacing-md);
}
.fulfillment-card__item {
display: flex;
align-items: center;
gap: var(--spacing-sm);
font-size: 0.875rem;
}
.fulfillment-card__item img {
width: 40px;
height: 40px;
object-fit: cover;
border-radius: var(--border-radius-sm);
}
/* Refunds */
.refund-card {
background: #fef3c7;
padding: var(--spacing-md);
border-radius: var(--border-radius);
margin-bottom: var(--spacing-md);
}
/* Mobile responsive */
@media (max-width: 767px) {
.order-items-table thead {
display: none;
}
.order-items-table tr {
display: block;
padding: var(--spacing-md) 0;
}
.order-items-table td {
display: block;
padding: var(--spacing-xs) 0;
border: none;
}
.order-items-table td:before {
content: attr(data-label);
font-weight: 500;
margin-right: var(--spacing-sm);
}
}

Practice Exercise

Build a complete order details page with:

  1. Order header with status badges
  2. Line items table with images
  3. Order summary with totals
  4. Shipping/billing addresses
  5. Fulfillment tracking info

Test by:

  • Placing test orders
  • Creating partial fulfillments
  • Processing refunds
  • Checking mobile responsiveness

Key Takeaways

  1. order object available on order template
  2. order.line_items for purchased products
  3. order.fulfillments for tracking info
  4. fulfillment.tracking_url for carrier links
  5. Status labels via financial_status_label
  6. format_address for address display
  7. Mobile-first approach for tables

What’s Next?

The next lesson covers Testing Account Flows to ensure everything works end-to-end.

Finished this lesson?

Mark it complete to track your progress.

Discussion

Loading comments...