This guide explains how to implement and use container settings in your Pack-powered Hydrogen storefront. Container settings provide consistent spacing, width, and alignment controls that can be applied to any section in your storefront.
What Are Container Settings?
Container settings are a reusable group of fields that control the layout container for sections throughout your storefront. They typically include:
- Section padding (top, bottom)
- Container width
- Container alignment
- Background color or image
- Content width constraints
By implementing container settings as a reusable function, you ensure consistent layout options across all your sections.
Implementing Container Settings
Here's how to implement container settings as a reusable module:
// app/settings/container.ts import { COLOR_PICKER_DEFAULTS } from '~/settings/common'; export function containerSettings() { return { label: 'Container Settings', name: 'container', component: 'group', description: 'Section padding, width, background', fields: [ // Padding settings { label: 'Section Padding', name: 'padding', component: 'group', fields: [ { label: 'Top Padding (desktop)', name: 'topDesktop', component: 'select', options: [ { label: 'None', value: 'md:pt-0' }, { label: 'Small', value: 'md:pt-4' }, { label: 'Medium', value: 'md:pt-8' }, { label: 'Large', value: 'md:pt-16' }, { label: 'Extra Large', value: 'md:pt-24' }, ], defaultValue: 'md:pt-16', }, { label: 'Bottom Padding (desktop)', name: 'bottomDesktop', component: 'select', options: [ { label: 'None', value: 'md:pb-0' }, { label: 'Small', value: 'md:pb-4' }, { label: 'Medium', value: 'md:pb-8' }, { label: 'Large', value: 'md:pb-16' }, { label: 'Extra Large', value: 'md:pb-24' }, ], defaultValue: 'md:pb-16', }, { label: 'Top Padding (mobile)', name: 'topMobile', component: 'select', options: [ { label: 'None', value: 'pt-0' }, { label: 'Small', value: 'pt-4' }, { label: 'Medium', value: 'pt-6' }, { label: 'Large', value: 'pt-10' }, { label: 'Extra Large', value: 'pt-16' }, ], defaultValue: 'pt-10', }, { label: 'Bottom Padding (mobile)', name: 'bottomMobile', component: 'select', options: [ { label: 'None', value: 'pb-0' }, { label: 'Small', value: 'pb-4' }, { label: 'Medium', value: 'pb-6' }, { label: 'Large', value: 'pb-10' }, { label: 'Extra Large', value: 'pb-16' }, ], defaultValue: 'pb-10', }, ], }, // Background settings { label: 'Background Settings', name: 'background', component: 'group', fields: [ { label: 'Background Type', name: 'type', component: 'radio-group', direction: 'horizontal', variant: 'radio', options: [ { label: 'None', value: 'none' }, { label: 'Color', value: 'color' }, { label: 'Image', value: 'image' }, ], defaultValue: 'none', }, { label: 'Background Color', name: 'color', component: 'color', colors: COLOR_PICKER_DEFAULTS, condition: { field: 'type', value: 'color' }, }, { label: 'Background Image', name: 'image', component: 'image', condition: { field: 'type', value: 'image' }, }, { label: 'Background Image Position', name: 'position', component: 'select', options: [ { label: 'Top', value: 'bg-top' }, { label: 'Center', value: 'bg-center' }, { label: 'Bottom', value: 'bg-bottom' }, ], defaultValue: 'bg-center', condition: { field: 'type', value: 'image' }, }, { label: 'Background Image Size', name: 'size', component: 'select', options: [ { label: 'Cover', value: 'bg-cover' }, { label: 'Contain', value: 'bg-contain' }, { label: 'Auto', value: 'bg-auto' }, ], defaultValue: 'bg-cover', condition: { field: 'type', value: 'image' }, }, ], }, // Container width settings { label: 'Container Width', name: 'width', component: 'select', options: [ { label: 'Full Width', value: 'w-full' }, { label: 'Extra Large (1280px)', value: 'max-w-screen-xl mx-auto' }, { label: 'Large (1024px)', value: 'max-w-screen-lg mx-auto' }, { label: 'Medium (768px)', value: 'max-w-screen-md mx-auto' }, { label: 'Small (640px)', value: 'max-w-screen-sm mx-auto' }, ], defaultValue: 'max-w-screen-xl mx-auto', }, // Content alignment { label: 'Content Alignment', name: 'alignment', component: 'select', options: [ { label: 'Left', value: 'text-left' }, { label: 'Center', value: 'text-center' }, { label: 'Right', value: 'text-right' }, ], defaultValue: 'text-left', }, ], defaultValue: { padding: { topDesktop: 'md:pt-16', bottomDesktop: 'md:pb-16', topMobile: 'pt-10', bottomMobile: 'pb-10', }, background: { type: 'none' }, width: 'max-w-screen-xl mx-auto', alignment: 'text-left', }, }; }
Using Container Settings in Section Schemas
// app/sections/HeroSection/HeroSection.schema.ts import { containerSettings } from '~/settings/container'; export function Schema() { return { category: 'Hero', label: 'Hero Section', key: 'hero-section', previewSrc: 'https://example.com/preview.jpg', fields: [ { label: 'Heading', name: 'heading', component: 'text', defaultValue: 'Welcome to our store', }, { label: 'Subheading', name: 'subheading', component: 'text', defaultValue: 'Discover our amazing products', }, containerSettings(), ], }; }
Applying Container Settings in Components
// app/sections/HeroSection/HeroSection.tsx import React from 'react'; import type { SectionProps } from '~/lib/types'; export function HeroSection({ data }: SectionProps) { const { heading, subheading, container } = data; const { padding, background, width, alignment } = container; const containerClasses = [ padding.topDesktop, padding.bottomDesktop, padding.topMobile, padding.bottomMobile, width, alignment, ].join(' '); let backgroundStyle = {}; if (background.type === 'color') { backgroundStyle = { backgroundColor: background.color! }; } else if (background.type === 'image' && background.image) { backgroundStyle = { backgroundImage: `url(${background.image.url})`, backgroundPosition: background.position, backgroundSize: background.size, }; } return ( <section className={`hero-section ${containerClasses}`} style={backgroundStyle}> <div className="hero-content"> <h1>{heading}</h1> <p>{subheading}</p> </div> </section> ); }
Reusable SectionContainer Component
// app/components/SectionContainer.tsx import React from 'react'; interface SectionContainerProps { container: { padding: { topDesktop: string; bottomDesktop: string; topMobile: string; bottomMobile: string; }; background: { type: 'none' | 'color' | 'image'; color?: string; image?: { url: string }; position?: string; size?: string; }; width: string; alignment: string; }; className?: string; children: React.ReactNode; } export function SectionContainer({ container, className = '', children, }: SectionContainerProps) { const { padding, background, width, alignment } = container; const containerClasses = [ padding.topDesktop, padding.bottomDesktop, padding.topMobile, padding.bottomMobile, width, alignment, className, ].join(' '); let backgroundStyle = {}; if (background.type === 'color') { backgroundStyle = { backgroundColor: background.color! }; } else if (background.type === 'image' && background.image) { backgroundStyle = { backgroundImage: `url(${background.image.url})`, backgroundPosition: background.position, backgroundSize: background.size, }; } return ( <section className={containerClasses} style={backgroundStyle}> {children} </section> ); }
Extending Container Settings
export function extendedContainerSettings(additionalOptions = {}) { const baseSettings = containerSettings(); const extendedFields = [ ...baseSettings.fields!, { label: 'Content Width', name: 'contentWidth', component: 'select', options: [ { label: 'Full Width', value: 'w-full' }, { label: 'Three-quarters', value: 'w-3/4 mx-auto' }, { label: 'Two-thirds', value: 'w-2/3 mx-auto' }, { label: 'Half', value: 'w-1/2 mx-auto' }, ], defaultValue: 'w-full', }, ...(additionalOptions.fields || []), ]; return { ...baseSettings, fields: extendedFields, defaultValue: { ...baseSettings.defaultValue!, contentWidth: 'w-full', ...(additionalOptions.defaultValue || {}), }, }; }
Theme Presets
// app/settings/theme-presets.ts export const LIGHT_PRESET = { background: { type: 'color', color: '#ffffff' }, textColor: '#000000', accentColor: '#3b82f6', }; export const DARK_PRESET = { background: { type: 'color', color: '#111827' }, textColor: '#ffffff', accentColor: '#60a5fa', }; export const BRAND_PRESET = { background: { type: 'color', color: '#f8fafc' }, textColor: '#334155', accentColor: '#0891b2', }; export function applyThemePreset(preset) { return { label: 'Theme Preset', name: 'themePreset', component: 'select', options: [ { label: 'Default', value: 'default' }, { label: 'Light', value: 'light' }, { label: 'Dark', value: 'dark' }, { label: 'Brand', value: 'brand' }, ], defaultValue: 'default', onChange: (field, form) => { if (field.value === 'light') { form.mutators.setValues({ container: { ...form.values.container, background: LIGHT_PRESET.background, }, textColor: LIGHT_PRESET.textColor, accentColor: LIGHT_PRESET.accentColor, }); } else if (field.value === 'dark') { form.mutators.setValues({ container: { ...form.values.container, background: DARK_PRESET.background, }, textColor: DARK_PRESET.textColor, accentColor: DARK_PRESET.accentColor, }); } else if (field.value === 'brand') { form.mutators.setValues({ container: { ...form.values.container, background: BRAND_PRESET.background, }, textColor: BRAND_PRESET.textColor, accentColor: BRAND_PRESET.accentColor, }); } }, }; }
Common Settings Module
// app/settings/common.ts export const COLOR_PICKER_DEFAULTS = [ { label: 'White', color: '#ffffff' }, { label: 'Black', color: '#000000' }, { label: 'Brand Primary', color: '#0891b2' }, { label: 'Brand Secondary', color: '#60a5fa' }, { label: 'Gray 100', color: '#f3f4f6' }, { label: 'Gray 200', color: '#e5e7eb' }, { label: 'Gray 500', color: '#6b7280' }, { label: 'Gray 900', color: '#111827' }, { label: 'Red', color: '#ef4444' }, { label: 'Yellow', color: '#eab308' }, { label: 'Green', color: '#22c55e' }, { label: 'Blue', color: '#3b82f6' }, { label: 'Purple', color: '#a855f7' }, ]; export const COLOR_SCHEMA_DEFAULT_VALUE = { white: '#ffffff', black: '#000000', brandPrimary: '#0891b2', brandSecondary: '#60a5fa', }; export const BUTTONS = [ { label: 'Primary', value: 'btn-primary' }, { label: 'Secondary', value: 'btn-secondary' }, { label: 'Tertiary', value: 'btn-tertiary' }, { label: 'Outline', value: 'btn-outline' }, { label: 'Accent', value: 'btn-accent' }, { label: 'Link', value: 'btn-link' }, ]; export const FLEX_POSITIONS = { desktop: [ { label: 'Top Left', value: 'md:justify-start md:items-start' }, { label: 'Top Center', value: 'md:justify-center md:items-start' }, { label: 'Top Right', value: 'md:justify-end md:items-start' }, { label: 'Middle Left', value: 'md:justify-start md:items-center' }, { label: 'Middle Center', value: 'md:justify-center md:items-center' }, { label: 'Middle Right', value: 'md:justify-end md:items-center' }, { label: 'Bottom Left', value: 'md:justify-start md:items-end' }, { label: 'Bottom Center', value: 'md:justify-center md:items-end' }, { label: 'Bottom Right', value: 'md:justify-end md:items-end' }, ], mobile: [ { label: 'Top Left', value: 'justify-start items-start' }, { label: 'Top Center', value: 'justify-center items-start' }, { label: 'Top Right', value: 'justify-end items-start' }, { label: 'Middle Left', value: 'justify-start items-center' }, { label: 'Middle Center', value: 'justify-center items-center' }, { label: 'Middle Right', value: 'justify-end items-center' }, { label: 'Bottom Left', value: 'justify-start items-end' }, { label: 'Bottom Center', value: 'justify-center items-end' }, { label: 'Bottom Right', value: 'justify-end items-end' }, ], }; export const OBJECT_POSITIONS = { desktop: [ { label: 'Top', value: 'md:object-top' }, { label: 'Center', value: 'md:object-center' }, { label: 'Bottom', value: 'md:object-bottom' }, { label: 'Left', value: 'md:object-left' }, { label: 'Right', value: 'md:object-right' }, { label: 'Top Left', value: 'md:object-left-top' }, { label: 'Top Right', value: 'md:object-right-top' }, { label: 'Bottom Left', value: 'md:object-left-bottom' }, { label: 'Bottom Right', value: 'md:object-right-bottom' }, ], mobile: [ { label: 'Top', value: 'object-top' }, { label: 'Center', value: 'object-center' }, { label: 'Bottom', value: 'object-bottom' }, { label: 'Left', value: 'object-left' }, { label: 'Right', value: 'object-right' }, { label: 'Top Left', value: 'object-left-top' }, { label: 'Top Right', value: 'object-right-top' }, { label: 'Bottom Left', value: 'object-left-bottom' }, { label: 'Bottom Right', value: 'object-right-bottom' }, ], };
Best Practices for Container Settings
- Use the same container settings function in all sections
- Keep class names consistent for spacing, alignment, and other styles
- Define section-specific container extensions in separate functions
Performance Considerations
- Optimize class generation to avoid unnecessary concatenations
- Consider memoizing container classes when they don't change
- Use utility classes rather than inline styles when possible
Accessibility Considerations
- Ensure background and text colors have sufficient contrast
- Include appropriate accessibility attributes in container elements
- Test container layouts with screen readers and keyboard navigation
Responsive Design
- Include separate settings for mobile and desktop layouts
- Use a mobile-first approach with responsive class modifiers
- Test containers at various viewport sizes
Troubleshooting
Settings Not Appearing
- Verify the
containerSettings()
function is included in your fields array - Check that it returns the correct schema structure
- Ensure you’re not exceeding field limits in your schema
Styles Not Applying
- Confirm you’re extracting container values correctly in your component
- Verify class names match your CSS framework (e.g., Tailwind CSS)
- Inspect the HTML to ensure classes are applied
Default Values Not Working
- Ensure
defaultValue
object matches your fields - Check that the component handles missing or undefined values
- Provide fallback values in your component if needed
Conclusion
Container settings provide a powerful way to ensure layout consistency across your Pack-powered Hydrogen storefront. By implementing them as reusable functions, you create a design system that can be easily maintained, extended, and customized by content editors without developer intervention.