liquid-theme-standards

安装量: 658
排名: #1764

安装

npx skills add https://github.com/benjaminsehl/liquid-skills --skill liquid-theme-standards

CSS, JS & HTML Standards for Shopify Liquid Themes Core Principles Progressive enhancement — semantic HTML first, CSS second, JS third No external dependencies — native browser APIs only for JavaScript Design tokens — never hardcode colors, spacing, or fonts BEM naming — consistent class naming throughout Defensive CSS — handle edge cases gracefully CSS in Liquid Themes Where CSS Lives Location Liquid? Use For {% stylesheet %} No Component-scoped styles (one per file) {% style %} Yes Dynamic values needing Liquid (e.g., color settings) assets/*.css No Shared/global styles Critical: {% stylesheet %} does NOT process Liquid. Use inline style attributes for dynamic values: {%- comment -%} Do: inline variables {%- endcomment -%} < div class = " hero " style = " --bg-color : {{ section . settings . bg_color }} ; --padding : {{ section . settings . padding }} px ; "

{%- comment -%} Don't: Liquid inside stylesheet {%- endcomment -%} {% stylesheet %} .hero { background: {{ section . settings . bg_color }} ; } / Won't work / {% endstylesheet %} BEM Naming Convention .block → Component root: .product-card .block__element → Child: .product-card__title .block--modifier → Variant: .product-card--featured .block__element--modifier → Element variant: .product-card__title--large Rules: Hyphens separate words: .product-card , not .productCard Single element level only: .block__element , never .block__el1__el2 Modifier always paired with base class: class="btn btn--primary" , never class="btn--primary" alone Start new BEM scope when a child could be standalone

< div class = " product-card "

< h3 class = " product-card__title "

{{ product.title }} </ h3

< span class = " product-card__button-label "

{{ 'add_to_cart' | t }} </ span

</ div

< div class = " product-card "

< button class = " button button--primary "

< span class = " button__label "

{{ 'add_to_cart' | t }} </ span

</ button

</ div

Specificity Target 0 1 0 (single class) wherever possible Maximum 0 4 0 for complex parent-child cases Never use IDs as selectors Never use !important (comment why if absolutely forced to) Avoid element selectors — use classes CSS Nesting / Do: media queries inside selectors / .header { width : 100 % ; @media screen and ( min-width : 750 px ) { width : auto ; } } / Do: state modifiers with & / .button { background : var ( --color-primary ) ; & :hover { background : var ( --color-primary-hover ) ; } & :focus-visible { outline : 2 px solid var ( --color-focus ) ; } & [ disabled ] { opacity : 0.5 ; } } / Do: parent modifier affecting children (single level) / .card--featured { .card__title { font-size : var ( --font-size-xl ) ; } } / Don't: nested beyond first level / .parent { .child { .grandchild { } / Too deep / } } Design Tokens Use CSS custom properties for all values — never hardcode colors, spacing, or fonts. Define a consistent scale and reference it everywhere. Example scale (adapt to your theme's needs): :root { / Spacing — use a consistent scale / --space-2xs : 0.5 rem ; --space-xs : 0.75 rem ; --space-sm : 1 rem ; --space-md : 1.5 rem ; --space-lg : 2 rem ; --space-xl : 3 rem ; / Typography — relative units / --font-size-sm : 0.875 rem ; --font-size-base : 1 rem ; --font-size-lg : 1.125 rem ; --font-size-xl : 1.25 rem ; --font-size-2xl : 1.5 rem ; } Key principles: Use rem for spacing and typography (respects user font size preferences) Name tokens semantically: --space-sm not --space-16 Define in :root for global tokens, on component root for scoped tokens CSS Variable Scoping Global — in :root for theme-wide values Component-scoped — on component root, namespaced: / Do: namespaced / .facets { --facets-padding : var ( --space-md ) ; --facets-z-index : 3 ; } / Don't: generic names that collide / .facets { --padding : var ( --space-md ) ; --z-index : 3 ; } Override via inline style for section/block settings: < section class = " hero " style = " --hero-bg: {{ section . settings . bg_color }} ; --hero-padding: {{ section . settings . padding }} px; "

CSS Property Order Layout — position , display , flex-direction , grid-template-columns Box model — width , margin , padding , border Typography — font-family , font-size , line-height , color Visual — background , opacity , border-radius Animation — transition , animation Logical Properties (RTL Support) / Do: logical properties / padding-inline : 2 rem ; padding-block : 1 rem ; margin-inline : auto ; border-inline-end : 1 px solid var ( --color-border ) ; text-align : start ; inset : 0 ; / Don't: physical properties / padding-left : 2 rem ; text-align : left ; top : 0 ; right : 0 ; bottom : 0 ; left : 0 ; Defensive CSS .component { overflow-wrap : break-word ; / Prevent text overflow / min-width : 0 ; / Allow flex items to shrink / max-width : 100 % ; / Constrain images/media / isolation : isolate ; / Create stacking context / } .image-container { aspect-ratio : 4 / 3 ; / Prevent layout shift / background : var ( --color-surface ) ; / Fallback for missing images / } Modern CSS Features / Container queries for responsive components / .product-grid { container-type : inline-size ; } @container ( min-width : 400 px ) { .product-card { grid-template-columns : 1 fr 1 fr ; } } / Fluid spacing / .section { padding : clamp ( 1 rem , 4 vw , 3 rem ) ; } / Intrinsic sizing / .content { width : min ( 100 % , 800 px ) ; } Performance Animate only transform and opacity (never layout properties) Use will-change sparingly — remove after animation Use contain: content for isolated rendering Use dvh instead of vh on mobile Reduced Motion @media ( prefers-reduced-motion : reduce ) { * , * ::before , * ::after { animation-duration : 0.01 ms !important ; animation-iteration-count : 1 !important ; transition-duration : 0.01 ms !important ; scroll-behavior : auto !important ; } } JavaScript in Liquid Themes Where JS Lives Location Liquid? Use For {% javascript %} No Component-specific scripts (one per file) assets/*.js No Shared utilities, Web Components Web Component Pattern class ProductCard extends HTMLElement { connectedCallback ( ) { this . button = this . querySelector ( '[data-add-to-cart]' ) ; this . button ?. addEventListener ( 'click' , this .

handleClick

. bind ( this ) ) ; } disconnectedCallback ( ) { // Clean up event listeners, abort controllers } async

handleClick

( event ) { event . preventDefault ( ) ; this . button . disabled = true ; try { const formData = new FormData ( ) ; formData . append ( 'id' , this . dataset . variantId ) ; formData . append ( 'quantity' , '1' ) ; const response = await fetch ( '/cart/add.js' , { method : 'POST' , body : formData } ) ; if ( ! response . ok ) throw new Error ( 'Failed' ) ; this . dispatchEvent ( new CustomEvent ( 'cart:item-added' , { detail : await response . json ( ) , bubbles : true } ) ) ; } catch ( error ) { console . error ( 'Add to cart error:' , error ) ; } finally { this . button . disabled = false ; } } } customElements . define ( 'product-card' , ProductCard ) ; < product-card data-variant-id = " {{ product . selected_or_first_available_variant . id }} "

< button data-add-to-cart

{{ 'products.add_to_cart' | t }} </ button

</ product-card

JavaScript Rules Rule Do Don't Loops for (const item of items) items.forEach() Async async / await .then() chains Variables const by default let unless reassigning Conditionals Early returns Nested if/else URLs new URL() + URLSearchParams String concatenation Dependencies Native browser APIs External libraries Private methods

methodName()

_methodName() Types JSDoc @typedef , @param , @returns Untyped AbortController for Fetch class DataLoader extends HTMLElement {

controller

= null ; async load ( url ) { this .

controller

?. abort ( ) ; this .

controller

= new AbortController ( ) ; try { const response = await fetch ( url , { signal : this .

controller

. signal } ) ; if ( ! response . ok ) throw new Error ( HTTP ${ response . status } ) ; return await response . json ( ) ; } catch ( error ) { if ( error . name !== 'AbortError' ) throw error ; return null ; } } disconnectedCallback ( ) { this .

controller

?. abort ( ) ; } } Component Communication Parent → Child: Call public methods this . querySelector ( 'child-component' ) ?. publicMethod ( data ) ; Child → Parent: Dispatch custom events this . dispatchEvent ( new CustomEvent ( 'child:action' , { detail : { value } , bubbles : true } ) ) ; HTML Standards Native Elements First Need Use Not Expandable

/ Custom accordion with JS Dialog/modal Custom overlay div Tooltip/popup popover attribute Custom positioned div Search form
返回排行榜