Demonstrate Service Page.
View this pattern on the live demo site
One service detail page wrapping the service section.
{% comment %}
Display a page for a single service datastore_item
{% endcomment -%}
{%- include "/_header.liquid" -%}
{%- include "/sections/_generic_banner.liquid" -%}
{%- include "/sections/_breadcrumbs.liquid" -%}
{%- include "/sections/_service.liquid" service:entity show_page_title:false section_class:'py-5 my-lg-2' -%}
{%- include "/sections/_cta_snippet.liquid" section_class:'mt-4 mb-5' -%}
{%- include "/_footer.liquid" -%}
Title, copy, and media for a single service datastore item.
{% comment %}
Outputs a section for a service datastore item with an optional sidebar
Inputs:
section_class
section_id
service - the service datastore item (defaults to entity when omitted)
show_page_title - passed through to _service_full.liquid
Custom Fields:
service - the service datastore item
Outputs:
section_rendered - true when this section outputs markup, false otherwise
{% endcomment -%}
{%- var service = service | default: entity -%}
{%- if service.is_valid -%}
<section{% if section_class %} class="{{ section_class }}"{% endif %}{% if section_id %} id="{{ section_id }}"{% endif %}>
<div class="container-xl py-lg-2">
<div class="row g-4 g-xl-5 align-items-start">
{%- capture service_full_output -%}
{%- include "/partials/_service_full.liquid" service:service show_page_title:show_page_title -%}
{%- endcapture -%}
{%- var has_sidebar = false %}{% comment %}Will be set by the _sidebar_content partial template{% endcomment -%}
{%- capture sidebar_output -%}
{%- include "/sections/_sidebar_content.liquid" embedded:true service:service -%}
{%- endcapture -%}
{%- if has_sidebar -%}
<div class="col-12 col-lg-8">
{{ service_full_output }}
</div>
<div class="col-12 col-lg-4">
{{ sidebar_output }}
</div>
{%- else -%}
<div class="col-12 col-lg-10 mx-auto">
{{ service_full_output }}
</div>
{%- endif -%}
</div>
</div>
</section>
{%- set section_rendered = true -%}
{%- else -%}
{%- set section_rendered = false -%}
{%- endif -%}
Custom fields owned by the service detail section.
[
{
"Type": "group",
"Label": "Service",
"Fields": [
{
"Type": "datastoreitem",
"FieldID": "service",
"Label": "Service",
"DatastoreType": "services"
}
]
}
]
Full service write-up: title, copy, and media.
{% comment %}
The full display for a service datastore item
Inputs:
service
show_page_title - when false, omits the title/subtitle block (use when the page banner already shows the title)
img_preset_service_full - The image preset to use for the full service image
{% endcomment -%}
{%- var show_page_title = show_page_title | default: true -%}
{%- if service.is_valid -%}
{%- add_stylesheet inline -%}
.service-body > *:first-child { margin-top: 0; }
.service-body h2 { margin-top: 2.25rem; margin-bottom: 0.85rem; font-size: 1.5rem; }
.service-body h3 { margin-top: 1.75rem; margin-bottom: 0.65rem; font-size: 1.2rem; }
.service-body p, .service-body ul { margin-bottom: 1.15rem; }
.service-body ul { padding-left: 1.2rem; }
{%- end_stylesheet -%}
<article class="service-full pe-lg-2">
{%- if show_page_title -%}
{%- include '/common/_title_with_subtitle.liquid' title:service.title subtitle:service.subtitle title_div_classname:'mb-4 mb-lg-5 text-start' title_classname:'text-primary text-start' subtitle_classname:'lead text-muted mb-0' -%}
{%- endif -%}
{%- if service.image.is_valid -%}
<figure class="service-image-container mb-4 mb-lg-5">
{%- img service.image link:false preset:img_preset_service_full class:'img-fluid rounded-3 w-100' -%}
</figure>
{%- endif -%}
{%- if service.content.is_valid or service.content_html.is_valid -%}
<div class="service-body fs-6 lh-lg">
{{ service.content | default: service.content_html }}
</div>
{%- endif -%}
</article>
{%- endif -%}
Optional sidebar column included from the service section.
{% comment %}
Renders optional sidebar content for service and similar pages.
Inputs:
embedded - when true, omits the outer section/container wrapper (for use inside another section column)
section_class
section_id
service - optional current service (used to exclude from related services)
Outputs:
has_sidebar - true if the sidebar is output and false if not
section_rendered - true when this section outputs markup. This is only set to false when embedded is not true (in order to prevent potential interference with the parent section template)
Custom Fields:
sidebar_intro
sidebar_html
sidebar_cta_url
sidebar_cta_label
sidebar_action_buttons (text, link)
sidebar_show_contact
sidebar_show_related
{% endcomment -%}
{%- if entity.sidebar_intro.is_valid %}{% set has_sidebar = true -%}
{%- elsif entity.sidebar_html.is_valid %}{% set has_sidebar = true -%}
{%- elsif entity.sidebar_cta_url.is_valid and entity.sidebar_cta_label.is_valid %}{% set has_sidebar = true -%}
{%- elsif entity.sidebar_action_buttons.is_valid %}{% set has_sidebar = true -%}
{%- elsif entity.sidebar_show_contact.on %}{% set has_sidebar = true -%}
{%- elsif entity.sidebar_show_related.on %}{% set has_sidebar = true -%}
{%- else %}{% set has_sidebar = false -%}
{%- endif -%}
{%- if has_sidebar -%}
{%- var sidebar_intro = entity.sidebar_intro -%}
{%- var sidebar_html = entity.sidebar_html -%}
{%- var sidebar_cta_url = entity.sidebar_cta_url.value -%}
{%- var sidebar_cta_label = entity.sidebar_cta_label -%}
{%- var sidebar_action_buttons = entity.sidebar_action_buttons -%}
{%- var sidebar_show_contact = entity.sidebar_show_contact.on -%}
{%- var sidebar_show_related = entity.sidebar_show_related.on -%}
{%- var current_service = service | default: entity -%}
{%- if embedded -%}
<aside class="sidebar-content sticky-lg-top" style="top: 1.5rem;">
{%- include '/partials/_sidebar_content.liquid' -%}
</aside>
{%- else -%}
<section{% if section_class %} class="{{ section_class }}"{% endif %}{% if section_id %} id="{{ section_id }}"{% endif %}>
<div class="container-xl">
<div class="row justify-content-end">
<div class="col-12 col-lg-4">
<aside class="sidebar-content">
{%- include '/partials/_sidebar_content.liquid' -%}
</aside>
</div>
</div>
</div>
</section>
{%- endif -%}
{%- set section_rendered = true -%}
{%- else -%}
{%- unless embedded -%}
{%- set section_rendered = false -%}
{%- endunless -%}
{%- endif -%}
Intro, HTML, CTA, and related-service fields owned by the sidebar.
[
{
"Type": "group",
"Label": "Sidebar Content",
"Collapsible": true,
"Fields": [
{
"Type": "textarea",
"FieldID": "sidebar_intro",
"Label": "Intro Text",
"Placeholder": "Optional short text displayed at the top of the sidebar"
},
{
"Type": "html",
"FieldID": "sidebar_html",
"Label": "Sidebar Details",
"Placeholder": "Optional extra HTML under the intro (tips, bullets, hours notes)"
},
{
"Type": "url",
"FieldID": "sidebar_cta_url",
"Label": "Primary CTA URL"
},
{
"Type": "text",
"FieldID": "sidebar_cta_label",
"Label": "Primary CTA Label",
"Placeholder": "e.g., Get a Free Estimate"
},
{
"Type": "checkbox",
"FieldID": "sidebar_show_contact",
"Label": "Show Shop Contact Card",
"DefaultValue": true
},
{
"Type": "checkbox",
"FieldID": "sidebar_show_related",
"Label": "Show Related Services",
"DefaultValue": true
},
{
"Type": "fieldset",
"FieldID": "sidebar_action_buttons",
"Label": "Buttons",
"Validators": [
{
"Message": "You may not have more than 3 buttons",
"Type": "maxitems",
"Value": 3
}
],
"Fields": [
{
"Type": "text",
"FieldID": "text",
"Label": "Button Text",
"IsRequired": true,
"RequiredMessage": "Button text is required",
"Placeholder": "e.g., All Services"
},
{
"Type": "url",
"FieldID": "link",
"Label": "Button Link",
"IsRequired": true,
"RequiredMessage": "Button link is required"
}
],
"AllowMultiple": true,
"InitCollapsed": false,
"Noun": "Button"
}
]
}
]
Sidebar markup (contact, related services, action buttons).
{% comment %}
Renders sidebar widgets (intro, CTA, contact, related services, action buttons)
Inputs:
sidebar_intro
sidebar_html
sidebar_cta_url
sidebar_cta_label
sidebar_action_buttons
sidebar_show_contact
sidebar_show_related
current_service - optional service to exclude from related list
theme_settings - optional; used for contact details when available
{% endcomment -%}
{%- var headinglevel = headinglevel %}{% comment %}Copy to current scope{% endcomment -%}
{%- var increment_headinglevel = false -%}
{%- var sidebar_show_contact = sidebar_show_contact | default: false -%}
{%- var sidebar_show_related = sidebar_show_related | default: false -%}
{%- if sidebar_intro is_valid or sidebar_cta_url is_valid or sidebar_html is_valid -%}
<div class="card border-0 shadow-sm text-bg-light mb-4">
<div class="card-body p-4">
{%- if sidebar_intro is_valid -%}
<div class="mb-3">{{ sidebar_intro }}</div>
{%- endif -%}
{%- if sidebar_html is_valid -%}
<div class="small mb-3">{{ sidebar_html }}</div>
{%- endif -%}
{%- if sidebar_cta_url is_valid and sidebar_cta_label is_valid -%}
<a href="{{ sidebar_cta_url }}" class="btn btn-primary w-100 fw-semibold text-uppercase">{{ sidebar_cta_label }}</a>
{%- endif -%}
</div>
</div>
{%- elsif sidebar_cta_url is_valid and sidebar_cta_label is_valid -%}
<div class="mb-4">
<a href="{{ sidebar_cta_url }}" class="btn btn-primary w-100 fw-semibold text-uppercase">{{ sidebar_cta_label }}</a>
</div>
{%- endif -%}
{%- if sidebar_show_contact and theme_settings.is_valid and theme_settings.footer_contact_items.is_valid -%}
<div class="card border-0 shadow-sm mb-4">
<div class="card-body p-4">
<p class="h6 text-uppercase fw-bold mb-3" style="letter-spacing: 0.06em;">{{ theme_settings.footer_contact_title | default: 'Visit Us' }}</p>
<ul class="list-unstyled mb-0">
{%- for var item in theme_settings.footer_contact_items -%}
<li class="d-flex align-items-start gap-2{% unless forloop.last %} mb-3{% endunless %}">
{%- if item.icon_class.is_valid -%}
<span class="text-primary flex-shrink-0" aria-hidden="true"><i class="{{ item.icon_class }}"></i></span>
{%- endif -%}
<span class="small">
{%- if item.is_link.value == 'link' or item.is_link.on -%}
<a href="{{ item.link.value }}" class="text-reset text-decoration-none">{{ item.text }}</a>
{%- else -%}
{{ item.text }}
{%- endif -%}
</span>
</li>
{%- endfor -%}
</ul>
</div>
</div>
{%- endif -%}
{%- if sidebar_action_buttons is_valid -%}
{%- var btn_class = 'btn-outline-primary' -%}
<div class="d-grid gap-2 mb-4">
{%- for button in sidebar_action_buttons -%}
<a href="{{ button.link.value }}" class="btn {{ btn_class }} fw-semibold text-uppercase">{{ button.text }}</a>
{%- endfor -%}
</div>
{%- endif -%}
{%- if sidebar_show_related -%}
{%- datastore_items var related_services = datastore:'Services' sort_by:'random' cache_random:true exclude:current_service has_url:true limit:5 -%}
{%- if related_services.is_valid -%}
<div class="card border-0 shadow-sm mb-4">
<div class="card-body p-4">
<p class="h6 text-uppercase fw-bold mb-3" style="letter-spacing: 0.06em;">More Services</p>
<ul class="list-unstyled mb-0 small">
{%- for sibling in related_services -%}
<li class="mb-2">
<a href="{{ sibling.full_url }}" class="text-decoration-none">{{ sibling.title }}</a>
</li>
{%- endfor -%}
</ul>
<p class="mb-0 mt-3"><a href="/shop" class="small fw-semibold">View all services</a></p>
</div>
</div>
{%- endif -%}
{%- endif -%}