Container Settings in Pack: Implementation and Usage

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

  1. Verify the containerSettings() function is included in your fields array
  2. Check that it returns the correct schema structure
  3. Ensure you’re not exceeding field limits in your schema

Styles Not Applying

  1. Confirm you’re extracting container values correctly in your component
  2. Verify class names match your CSS framework (e.g., Tailwind CSS)
  3. Inspect the HTML to ensure classes are applied

Default Values Not Working

  1. Ensure defaultValue object matches your fields
  2. Check that the component handles missing or undefined values
  3. 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.

Was this page helpful?