Skip to Content
Getting StartedBuilding Your First Template

Building Your First Template

This tutorial walks you through modifying the scaffolded template into a polished design. By the end, you’ll have a working template ready for validation.

What we’ll build

We’ll customise the skeleton template into a clean, modern design with:

  • A sticky navigation header with mobile menu
  • A hero section on the home page
  • Custom colour scheme using CSS custom properties
  • Full RTL (right-to-left) support
  • Dark and light mode styles

Step by step

Customise your colour scheme

Open template.css and define your template’s colour palette:

:root { --color-accent: #e11d48; --color-secondary: #9333ea; --font-heading: 'Sora', sans-serif; --font-body: 'Inter', sans-serif; --border-radius: 12px; --header-height: 72px; } [data-theme="light"] { --bg-primary: #ffffff; --bg-secondary: #f8fafc; --text-primary: #0f172a; --text-secondary: #64748b; } [data-theme="dark"] { --bg-primary: #0f172a; --bg-secondary: #1e293b; --text-primary: #f8fafc; --text-secondary: #94a3b8; }

These custom properties are available throughout your template. Gymlium’s platform reads them to ensure consistent theming.

Build the Header component

Open Header/Header.tsx and replace the skeleton with a real navigation bar:

import styles from './Header.module.css' import { useApp } from '@/hooks/useApp' import { useAuth } from '@/hooks/useAuth' export function Header() { const { clientName, localePath, t, isRtl } = useApp() const { user } = useAuth() return ( <header className={styles.header}> <div className={styles.container}> <a href={localePath('/')} className={styles.logo}> {clientName} </a> <nav className={styles.nav}> <a href={localePath('/')}>{t('home')}</a> <a href={localePath('/about')}>{t('about')}</a> <a href={localePath('/services')}>{t('services')}</a> <a href={localePath('/contact')}>{t('contact')}</a> </nav> <div className={styles.actions}> {user ? ( <a href={localePath('/account')}>{t('myAccount')}</a> ) : ( <a href={localePath('/login')} className={styles.loginBtn}> {t('login')} </a> )} </div> </div> </header> ) }

Always use t() for user-facing text and localePath() for links. Never hardcode strings — Gymlium supports 14 languages including Arabic and Persian.

Style the Header

Open Header/Header.module.css:

.header { position: sticky; top: 0; z-index: 100; height: var(--header-height); background: var(--bg-primary); border-bottom: 1px solid var(--text-secondary); display: flex; align-items: center; } .container { max-width: 1200px; margin: 0 auto; padding: 0 1.5rem; width: 100%; display: flex; align-items: center; justify-content: space-between; } .logo { font-family: var(--font-heading); font-size: 1.25rem; font-weight: 700; color: var(--text-primary); text-decoration: none; } .nav { display: flex; gap: 2rem; } .nav a { color: var(--text-secondary); text-decoration: none; font-size: 0.9rem; transition: color 0.2s; } .nav a:hover { color: var(--color-accent); } .loginBtn { background: var(--color-accent); color: #fff; padding: 0.5rem 1.25rem; border-radius: var(--border-radius); text-decoration: none; font-weight: 600; font-size: 0.9rem; } :global([dir="rtl"]) .nav { flex-direction: row-reverse; }

Create the home page hero section

Open pages/home/HomeContent.tsx:

import styles from './HomeContent.module.css' import { useApp } from '@/hooks/useApp' export default function HomeContent() { const { clientName, t, localePath } = useApp() return ( <main className={styles.page}> <section className={styles.hero}> <h1 className={styles.title}> {t('welcomeTo')} {clientName} </h1> <p className={styles.subtitle}> {t('heroSubtitle')} </p> <div className={styles.cta}> <a href={localePath('/services')} className={styles.primaryBtn}> {t('viewServices')} </a> <a href={localePath('/about')} className={styles.secondaryBtn}> {t('learnMore')} </a> </div> </section> </main> ) }

Add RTL support

Gymlium supports right-to-left languages (Arabic, Persian). Every template must work correctly in RTL mode. The platform sets dir="rtl" on the <html> element — you handle the CSS overrides.

/* In HomeContent.module.css */ .cta { display: flex; gap: 1rem; } :global([dir="rtl"]) .cta { flex-direction: row-reverse; } :global([dir="rtl"]) .hero { text-align: right; }

See the RTL Layout Guide for comprehensive RTL patterns.

Validate your template

Run the validation script to confirm everything is correct:

npm run validate -- --template my-awesome-template

A successful validation outputs:

✓ template.manifest.json is valid ✓ All required files present ✓ Header export found (named export) ✓ Footer export found (named export) ✓ Loading export found (default export) ✓ All page components have default exports ✓ template.css contains required custom properties ✓ Thumbnail image found (1200x900, PNG) Validation passed! Your template is ready for submission.

Common patterns

// Use useState for mobile menu toggle const [menuOpen, setMenuOpen] = useState(false) return ( <> <button className={styles.hamburger} onClick={() => setMenuOpen(!menuOpen)} aria-label={t('toggleMenu')} > ☰ </button> {menuOpen && ( <nav className={styles.mobileNav}> {/* links */} </nav> )} </> )

Next steps

Last updated on