Skip to Content
ReferenceLayout Components

Layout Components

Layout components wrap every page of a trainer’s website. They are always visible regardless of which page the visitor is on.

Trainer logos (required pattern)

Trainers upload optional logos for light and dark mode from the Gymlium dashboard or signup wizard. Your template must not ship a business-specific logo file.

Use the platform TemplateLogo component in Header and Footer:

import { TemplateLogo } from '@/components/TemplateLogo' <TemplateLogo size="header" className={styles.logoImage} fallback={<span className={styles.wordmark}>{clientName}</span>} />
  • If the trainer uploaded logos, TemplateLogo swaps them by theme (data-theme on <html>).
  • If not, your fallback renders — typically a styled wordmark of clientName.
  • Recommended sizes: header clamp(38px, 3.2vw, 60px) max width ~250px; footer 36px.
  • Declare support in template.manifest.json under branding.logo (see Manifest).

Do not import static logo PNGs for the trainer’s business into your template package. Preview/thumbnail assets in preview/ are for the marketplace listing only.

Favicon (handled by the platform)

The browser-tab favicon is not part of your template. Trainers upload an optional favicon in the signup wizard and in Dashboard → Appearance, and Gymlium injects it into the document <head> automatically for every page (falling back to the Gymlium icon when none is set).

  • Do not add a favicon.ico, icon.png, or apple-icon.png to your template, and do not render a <link rel="icon"> tag — it would compete with the trainer’s chosen favicon.
  • Optionally declare branding.favicon.supported: true in your manifest to confirm your template follows this rule (see Manifest).

The Header is the main navigation bar displayed at the top of every page. It must be a named export.

Responsibilities

  • Display the trainer’s business name or logo
  • Primary navigation links
  • Locale/language switcher
  • Authentication state (login button or account link)
  • Mobile-responsive hamburger menu

Example implementation

import { useState } from 'react' import styles from './Header.module.css' import { useApp } from '@/hooks/useApp' import { useAuth } from '@/hooks/useAuth' export function Header() { const { clientName, localePath, t, isRtl, locale, locales } = useApp() const { user } = useAuth() const [mobileMenuOpen, setMobileMenuOpen] = useState(false) const navLinks = [ { href: localePath('/'), label: t('home') }, { href: localePath('/about'), label: t('about') }, { href: localePath('/services'), label: t('services') }, { href: localePath('/blog'), label: t('blog') }, { href: localePath('/contact'), label: t('contact') }, ] return ( <header className={styles.header}> <div className={styles.inner}> <a href={localePath('/')} className={styles.logo}> {clientName} </a> <nav className={styles.desktopNav}> {navLinks.map(link => ( <a key={link.href} href={link.href}> {link.label} </a> ))} </nav> <div className={styles.actions}> {/* Locale switcher */} <select value={locale} onChange={e => { window.location.href = `/${e.target.value}` }} className={styles.localeSwitcher} > {locales.map(loc => ( <option key={loc} value={loc}>{loc.toUpperCase()}</option> ))} </select> {/* Auth */} {user ? ( <a href={localePath('/account')}>{t('myAccount')}</a> ) : ( <a href={localePath('/login')} className={styles.loginBtn}> {t('login')} </a> )} </div> {/* Mobile menu toggle */} <button className={styles.hamburger} onClick={() => setMobileMenuOpen(!mobileMenuOpen)} aria-label={t('toggleMenu')} aria-expanded={mobileMenuOpen} > <span /> <span /> <span /> </button> </div> {/* Mobile menu */} {mobileMenuOpen && ( <nav className={styles.mobileNav}> {navLinks.map(link => ( <a key={link.href} href={link.href}> {link.label} </a> ))} </nav> )} </header> ) }

Export pattern

// index.ts export { Header } from './Header/Header'

Header must be a named export, not a default export. The platform imports it as { Header }.

The Footer appears at the bottom of every page. It must be a named export.

Responsibilities

  • Secondary navigation links
  • Social media links
  • Copyright notice
  • Attribution link (for free_with_attribution templates)

Example implementation

import styles from './Footer.module.css' import { useApp } from '@/hooks/useApp' export function Footer() { const { clientName, localePath, t } = useApp() const year = new Date().getFullYear() return ( <footer className={styles.footer}> <div className={styles.inner}> <div className={styles.brand}> <span className={styles.name}>{clientName}</span> <p className={styles.tagline}>{t('footerTagline')}</p> </div> <nav className={styles.links}> <a href={localePath('/about')}>{t('about')}</a> <a href={localePath('/services')}>{t('services')}</a> <a href={localePath('/contact')}>{t('contact')}</a> <a href={localePath('/privacy')}>{t('privacy')}</a> <a href={localePath('/terms')}>{t('terms')}</a> </nav> <p className={styles.copyright}> © {year} {clientName}. {t('allRightsReserved')} </p> </div> </footer> ) }

Export pattern

// index.ts export { Footer } from './Footer/Footer'

Loading

The Loading component is displayed while page content is being fetched. It must be a default export.

Responsibilities

  • Visual loading indicator (spinner, skeleton, or animation)
  • Lightweight and fast-rendering
  • Consistent with your template’s design language

Example implementation

import styles from './Loading.module.css' export default function Loading() { return ( <div className={styles.container}> <div className={styles.spinner} /> </div> ) }
/* Loading.module.css */ .container { display: flex; align-items: center; justify-content: center; min-height: 200px; } .spinner { width: 40px; height: 40px; border: 3px solid var(--text-secondary); border-top-color: var(--color-accent); border-radius: 50%; animation: spin 0.8s linear infinite; } @keyframes spin { to { transform: rotate(360deg); } }

Export pattern

// index.ts export { default as Loading } from './Loading/Loading'

The Loading component should render quickly and avoid heavy dependencies. It’s the first thing visitors see while a page loads — keep it simple and performant.

Last updated on