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:

  1. Account overview with customer info
  2. Order history table/cards
  3. Address management (add, edit, delete, set default)
  4. Responsive layouts

Test by:

  • Creating orders on a test account
  • Adding/editing addresses
  • Setting default address
  • Viewing on mobile

Key Takeaways

  1. customer.orders provides order history
  2. order.customer_url links to order details
  3. format_address filter for addresses
  4. customer_address form for CRUD operations
  5. _method: delete for address deletion
  6. address[default] sets default address
  7. 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...