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:
- Order header with status badges
- Line items table with images
- Order summary with totals
- Shipping/billing addresses
- Fulfillment tracking info
Test by:
- Placing test orders
- Creating partial fulfillments
- Processing refunds
- Checking mobile responsiveness
Key Takeaways
orderobject available on order templateorder.line_itemsfor purchased productsorder.fulfillmentsfor tracking infofulfillment.tracking_urlfor carrier links- Status labels via
financial_status_label format_addressfor address display- 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...