Customer Account Pages Intermediate 12 min read
Account Overview: Orders and Addresses
Build a comprehensive account dashboard showing order history, saved addresses, and customer information with management capabilities.
The account overview is the customer’s home base. A well-organized dashboard helps customers quickly find orders, manage addresses, and update their information.
Account Template Structure
{# templates/customers/account.liquid #}
{% section 'customer-account' %}Account Overview Section
{# sections/customer-account.liquid #}
<section class="customer-account section-{{ section.id }}"> <div class="container"> <header class="customer-account__header"> <h1 class="customer-account__heading"> {{ section.settings.heading | default: 'My Account' }} </h1> <a href="{{ routes.account_logout_url }}" class="customer-account__logout"> Log out </a> </header>
<div class="customer-account__grid"> {# Customer info #} <div class="customer-account__info"> <h2>Account Details</h2> <p><strong>{{ customer.name }}</strong></p> <p>{{ customer.email }}</p>
{%- if customer.default_address -%} <h3>Default Address</h3> <address> {{ customer.default_address | format_address }} </address> {%- endif -%}
<a href="{{ routes.account_addresses_url }}" class="button button--secondary"> Manage Addresses </a> </div>
{# Order history #} <div class="customer-account__orders"> <h2>Order History</h2>
{%- if customer.orders.size > 0 -%} <table class="customer-account__orders-table"> <thead> <tr> <th>Order</th> <th>Date</th> <th>Status</th> <th>Total</th> </tr> </thead> <tbody> {%- for order in customer.orders -%} <tr> <td> <a href="{{ order.customer_url }}">{{ order.name }}</a> </td> <td> {{ order.created_at | date: format: 'abbreviated_date' }} </td> <td> <span class="order-status order-status--{{ order.financial_status }}"> {{ order.financial_status_label }} </span> </td> <td>{{ order.total_price | money }}</td> </tr> {%- endfor -%} </tbody> </table> {%- else -%} <p class="customer-account__empty"> You haven't placed any orders yet. </p> <a href="{{ routes.all_products_collection_url }}" class="button button--primary"> Start Shopping </a> {%- endif -%} </div> </div> </div></section>
{% schema %}{ "name": "Customer Account", "settings": [ { "type": "text", "id": "heading", "label": "Heading", "default": "My Account" } ]}{% endschema %}Order Object Properties
{# Available on each order #}{{ order.id }} {# Order ID #}{{ order.name }} {# Order name (#1001) #}{{ order.order_number }} {# Order number (1001) #}{{ order.created_at }} {# Order date #}{{ order.customer_url }} {# Link to order details #}
{# Pricing #}{{ order.subtotal_price }} {# Subtotal #}{{ order.total_price }} {# Total including tax/shipping #}{{ order.total_discounts }} {# Discount amount #}{{ order.shipping_price }} {# Shipping cost #}{{ order.total_tax }} {# Tax amount #}
{# Status #}{{ order.financial_status }} {# paid, pending, refunded, etc. #}{{ order.financial_status_label }} {# Translated status #}{{ order.fulfillment_status }} {# fulfilled, partial, unfulfilled #}{{ order.fulfillment_status_label }}{{ order.cancelled }} {# true/false #}{{ order.cancelled_at }} {# Cancellation date #}{{ order.cancel_reason }} {# Cancellation reason #}
{# Line items #}{{ order.line_items }} {# Array of line items #}{{ order.item_count }} {# Total item count #}Enhanced Order History
{# More detailed order cards #}{%- for order in customer.orders -%} <div class="order-card"> <header class="order-card__header"> <div class="order-card__info"> <a href="{{ order.customer_url }}" class="order-card__number"> {{ order.name }} </a> <time class="order-card__date"> {{ order.created_at | date: '%B %d, %Y' }} </time> </div>
<div class="order-card__status"> <span class="badge badge--{{ order.financial_status }}"> {{ order.financial_status_label }} </span> {%- if order.fulfillment_status -%} <span class="badge badge--{{ order.fulfillment_status }}"> {{ order.fulfillment_status_label }} </span> {%- endif -%} </div> </header>
{# Order items preview #} <div class="order-card__items"> {%- for line_item in order.line_items limit: 3 -%} <div class="order-card__item"> {%- if line_item.image -%} {{ line_item.image | image_url: width: 80 | image_tag: loading: 'lazy', class: 'order-card__item-image' }} {%- endif -%} <div class="order-card__item-info"> <p class="order-card__item-title">{{ line_item.title }}</p> <p class="order-card__item-qty">Qty: {{ line_item.quantity }}</p> </div> </div> {%- endfor -%}
{%- if order.line_items.size > 3 -%} <p class="order-card__more"> + {{ order.line_items.size | minus: 3 }} more items </p> {%- endif -%} </div>
<footer class="order-card__footer"> <span class="order-card__total"> Total: {{ order.total_price | money }} </span> <a href="{{ order.customer_url }}" class="button button--secondary button--small"> View Details </a> </footer> </div>{%- endfor -%}Addresses Template
{# templates/customers/addresses.liquid #}
{% section 'customer-addresses' %}Addresses Section
{# sections/customer-addresses.liquid #}
<section class="customer-addresses section-{{ section.id }}"> <div class="container"> <header class="customer-addresses__header"> <h1>Your Addresses</h1> <a href="{{ routes.account_url }}">← Back to Account</a> </header>
{# Add new address form #} <div class="customer-addresses__new"> <button type="button" class="customer-addresses__add-button" data-toggle-new-address > Add New Address </button>
<div class="customer-addresses__form-wrapper" id="new-address-form" hidden> {%- form 'customer_address', customer.new_address, class: 'address-form' -%} {% render 'address-form-fields', form: form %}
<div class="address-form__actions"> <button type="submit" class="button button--primary"> Add Address </button> <button type="button" class="button button--secondary" data-cancel-new-address> Cancel </button> </div> {%- endform -%} </div> </div>
{# Existing addresses #} {%- if customer.addresses.size > 0 -%} <div class="customer-addresses__grid"> {%- for address in customer.addresses -%} <div class="address-card" id="address-{{ address.id }}"> <div class="address-card__content"> {%- if address == customer.default_address -%} <span class="address-card__default-badge">Default</span> {%- endif -%}
<address> {{ address | format_address }} </address> </div>
<div class="address-card__actions"> <button type="button" class="button button--small" data-edit-address="{{ address.id }}"> Edit </button>
{%- form 'customer_address', address -%} <input type="hidden" name="_method" value="delete"> <button type="submit" class="button button--small button--danger" onclick="return confirm('Are you sure?')" > Delete </button> {%- endform -%}
{%- unless address == customer.default_address -%} {%- form 'customer_address', address -%} <input type="hidden" name="address[default]" value="1"> <button type="submit" class="button button--small"> Set as Default </button> {%- endform -%} {%- endunless -%} </div>
{# Edit form (hidden by default) #} <div class="address-card__edit-form" id="edit-address-{{ address.id }}" hidden> {%- form 'customer_address', address, class: 'address-form' -%} {% render 'address-form-fields', form: form, address: address %}
<div class="address-form__actions"> <button type="submit" class="button button--primary"> Save Changes </button> <button type="button" class="button button--secondary" data-cancel-edit="{{ address.id }}"> Cancel </button> </div> {%- endform -%} </div> </div> {%- endfor -%} </div> {%- else -%} <p class="customer-addresses__empty"> You haven't saved any addresses yet. </p> {%- endif -%} </div></section>
<script> // Toggle new address form document.querySelectorAll('[data-toggle-new-address]').forEach(btn => { btn.addEventListener('click', () => { document.getElementById('new-address-form').hidden = false; btn.hidden = true; }); });
document.querySelectorAll('[data-cancel-new-address]').forEach(btn => { btn.addEventListener('click', () => { document.getElementById('new-address-form').hidden = true; document.querySelector('[data-toggle-new-address]').hidden = false; }); });
// Toggle edit forms document.querySelectorAll('[data-edit-address]').forEach(btn => { btn.addEventListener('click', () => { const id = btn.dataset.editAddress; document.getElementById('edit-address-' + id).hidden = false; }); });
document.querySelectorAll('[data-cancel-edit]').forEach(btn => { btn.addEventListener('click', () => { const id = btn.dataset.cancelEdit; document.getElementById('edit-address-' + id).hidden = true; }); });</script>
{% schema %}{ "name": "Customer Addresses", "settings": []}{% endschema %}Address Form Fields Snippet
{# snippets/address-form-fields.liquid #}
<div class="address-form__fields"> <div class="address-form__row"> <div class="address-form__field"> <label for="{{ form.id }}-first-name">First name</label> <input type="text" id="{{ form.id }}-first-name" name="address[first_name]" value="{{ form.first_name }}" autocomplete="given-name" required > </div>
<div class="address-form__field"> <label for="{{ form.id }}-last-name">Last name</label> <input type="text" id="{{ form.id }}-last-name" name="address[last_name]" value="{{ form.last_name }}" autocomplete="family-name" required > </div> </div>
<div class="address-form__field"> <label for="{{ form.id }}-company">Company (optional)</label> <input type="text" id="{{ form.id }}-company" name="address[company]" value="{{ form.company }}" autocomplete="organization" > </div>
<div class="address-form__field"> <label for="{{ form.id }}-address1">Address</label> <input type="text" id="{{ form.id }}-address1" name="address[address1]" value="{{ form.address1 }}" autocomplete="address-line1" required > </div>
<div class="address-form__field"> <label for="{{ form.id }}-address2">Apartment, suite, etc. (optional)</label> <input type="text" id="{{ form.id }}-address2" name="address[address2]" value="{{ form.address2 }}" autocomplete="address-line2" > </div>
<div class="address-form__row"> <div class="address-form__field"> <label for="{{ form.id }}-city">City</label> <input type="text" id="{{ form.id }}-city" name="address[city]" value="{{ form.city }}" autocomplete="address-level2" required > </div>
<div class="address-form__field"> <label for="{{ form.id }}-country">Country/Region</label> <select id="{{ form.id }}-country" name="address[country]" data-address-country autocomplete="country" required > {{ all_country_option_tags }} </select> </div> </div>
<div class="address-form__row"> <div class="address-form__field"> <label for="{{ form.id }}-province">State/Province</label> <select id="{{ form.id }}-province" name="address[province]" data-address-province autocomplete="address-level1" > {# Populated by JS based on country #} </select> </div>
<div class="address-form__field"> <label for="{{ form.id }}-zip">Postal/ZIP code</label> <input type="text" id="{{ form.id }}-zip" name="address[zip]" value="{{ form.zip }}" autocomplete="postal-code" required > </div> </div>
<div class="address-form__field"> <label for="{{ form.id }}-phone">Phone (optional)</label> <input type="tel" id="{{ form.id }}-phone" name="address[phone]" value="{{ form.phone }}" autocomplete="tel" > </div>
<div class="address-form__field address-form__field--checkbox"> <label> <input type="checkbox" name="address[default]" value="1" {% if form.default %}checked{% endif %} > Set as default address </label> </div></div>Account Styles
.customer-account { padding: var(--spacing-2xl) 0;}
.customer-account__header { display: flex; justify-content: space-between; align-items: center; margin-bottom: var(--spacing-xl);}
.customer-account__heading { font-size: clamp(1.75rem, 4vw, 2.5rem); margin: 0;}
.customer-account__logout { font-size: 0.875rem; color: var(--color-text-light);}
.customer-account__grid { display: grid; gap: var(--spacing-xl);}
@media (min-width: 768px) { .customer-account__grid { grid-template-columns: 300px 1fr; }}
.customer-account__info { background: var(--color-background-subtle); padding: var(--spacing-lg); border-radius: var(--border-radius);}
.customer-account__info h2 { font-size: 1.25rem; margin-bottom: var(--spacing-md);}
.customer-account__info h3 { font-size: 0.875rem; text-transform: uppercase; letter-spacing: 0.05em; margin-top: var(--spacing-lg); margin-bottom: var(--spacing-sm);}
.customer-account__info address { font-style: normal; color: var(--color-text-light);}
/* Orders table */.customer-account__orders-table { width: 100%; border-collapse: collapse;}
.customer-account__orders-table th,.customer-account__orders-table td { padding: var(--spacing-sm) var(--spacing-md); text-align: left; border-bottom: 1px solid var(--color-border);}
.customer-account__orders-table th { font-size: 0.75rem; text-transform: uppercase; letter-spacing: 0.05em; color: var(--color-text-light);}
.order-status { display: inline-block; padding: 2px 8px; border-radius: 9999px; font-size: 0.75rem; font-weight: 500;}
.order-status--paid { background: #d1fae5; color: #065f46;}
.order-status--pending { background: #fef3c7; color: #92400e;}
.order-status--refunded { background: #fee2e2; color: #b91c1c;}
/* Order cards */.order-card { border: 1px solid var(--color-border); border-radius: var(--border-radius); padding: var(--spacing-md); margin-bottom: var(--spacing-md);}
.order-card__header { display: flex; justify-content: space-between; margin-bottom: var(--spacing-md);}
.order-card__number { font-weight: 600;}
.order-card__items { display: flex; gap: var(--spacing-sm); flex-wrap: wrap;}
.order-card__item { display: flex; gap: var(--spacing-sm); align-items: center;}
.order-card__item-image { width: 50px; height: 50px; object-fit: cover; border-radius: var(--border-radius-sm);}
.order-card__footer { display: flex; justify-content: space-between; align-items: center; margin-top: var(--spacing-md); padding-top: var(--spacing-md); border-top: 1px solid var(--color-border);}
/* Addresses */.customer-addresses__grid { display: grid; gap: var(--spacing-md); grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));}
.address-card { border: 1px solid var(--color-border); border-radius: var(--border-radius); padding: var(--spacing-md);}
.address-card__default-badge { display: inline-block; background: var(--color-primary); color: white; font-size: 0.75rem; padding: 2px 8px; border-radius: 9999px; margin-bottom: var(--spacing-sm);}
.address-card__actions { display: flex; gap: var(--spacing-xs); margin-top: var(--spacing-md); flex-wrap: wrap;}
/* Address form */.address-form__row { display: grid; gap: var(--spacing-md); grid-template-columns: 1fr 1fr;}
.address-form__field { margin-bottom: var(--spacing-md);}
.address-form__field label { display: block; font-size: 0.875rem; font-weight: 500; margin-bottom: var(--spacing-xs);}
.address-form__field input,.address-form__field select { width: 100%; padding: var(--spacing-sm); border: 1px solid var(--color-border); border-radius: var(--border-radius);}
.address-form__actions { display: flex; gap: var(--spacing-sm); margin-top: var(--spacing-md);}Practice Exercise
Build account pages with:
- Account overview with customer info
- Order history table/cards
- Address management (add, edit, delete, set default)
- Responsive layouts
Test by:
- Creating orders on a test account
- Adding/editing addresses
- Setting default address
- Viewing on mobile
Key Takeaways
customer.ordersprovides order historyorder.customer_urllinks to order detailsformat_addressfilter for addressescustomer_addressform for CRUD operations_method: deletefor address deletionaddress[default]sets default address- Status labels via
financial_status_label
What’s Next?
The next lesson covers the Order Details Page for in-depth order information.
Finished this lesson?
Mark it complete to track your progress.
Discussion
Loading comments...