Best Practices
Follow these guidelines to create high-quality templates that pass review, sell well, and make trainers happy.
Performance
Lazy loading
Use lazy loading for images below the fold:
<img
src={imageSrc}
alt={altText}
loading="lazy"
width={800}
height={600}
/>Optimised images
- Use appropriate image formats (WebP preferred, PNG for transparency)
- Serve responsive sizes with
srcsetwhere possible - Set explicit
widthandheightto prevent layout shift
Minimal JavaScript
Keep client-side JavaScript lightweight. Templates should feel fast:
- Avoid heavy animation libraries — use CSS animations and transitions
- Don’t import large utility libraries for one function
- Use dynamic imports for components not visible on initial load
CSS performance
- Avoid deeply nested selectors
- Minimise use of
*selectors - Use
will-changesparingly — only on elements that actually animate - Prefer
transformandopacityfor animations (they’re GPU-accelerated)
Accessibility
Semantic HTML
Use HTML elements for their intended purpose:
// ✅ Correct
<nav>...</nav>
<main>...</main>
<header>...</header>
<footer>...</footer>
<button onClick={handleClick}>Click me</button>
// ❌ Incorrect
<div className="nav">...</div>
<div className="main">...</div>
<div onClick={handleClick}>Click me</div>ARIA attributes
Add ARIA attributes where semantic HTML isn’t sufficient:
<button
aria-label={t('toggleMenu')}
aria-expanded={menuOpen}
onClick={() => setMenuOpen(!menuOpen)}
>
<HamburgerIcon />
</button>
<nav aria-label="Main navigation">
{/* links */}
</nav>
<div role="alert" aria-live="polite">
{errorMessage}
</div>Keyboard navigation
Ensure all interactive elements are keyboard-accessible:
- All clickable elements should be
<button>or<a>elements - Focus order should follow visual order
- Modals and mobile menus should trap focus
Escapeshould close modals and overlays- Visible focus indicators on all interactive elements
.button:focus-visible {
outline: 2px solid var(--color-accent);
outline-offset: 2px;
}Colour contrast
Ensure text meets WCAG AA contrast ratios:
- Normal text: minimum 4.5:1 contrast ratio
- Large text (18px+ bold, 24px+ regular): minimum 3:1 contrast ratio
- Check contrast in both light and dark modes
Code quality
Clean CSS
- Use consistent naming conventions (camelCase for CSS Modules)
- Group related properties logically
- Add comments only for non-obvious design decisions
No inline styles
Avoid inline styles — they can’t be themed and make RTL overrides difficult:
// ❌ Avoid
<div style={{ marginLeft: '1rem', color: '#333' }}>
// ✅ Prefer
<div className={styles.container}>Exception: truly dynamic values like style={"{{ width: percentage + '%' }}"} are acceptable.
Component organisation
- One component per file
- Co-locate CSS modules with their components
- Keep components focused — extract sub-components when complexity grows
- Name files consistently:
ComponentName.tsx+ComponentName.module.css
What reviewers look for
During the review process, Gymlium admins evaluate:
| Area | What they check |
|---|---|
| Functionality | All pages render, navigation works, no console errors |
| RTL support | Layout mirrors correctly in Arabic, no overlapping elements |
| Dark/light mode | Both modes look polished, no invisible text or missing borders |
| Responsiveness | Mobile menu works, layouts adapt, no horizontal scroll |
| Performance | Pages load quickly, no layout shift, smooth animations |
| Accessibility | Keyboard navigation, semantic HTML, ARIA labels |
| Code quality | Clean CSS, no inline styles, correct export patterns |
| Translations | No hardcoded text — all user-facing strings use t() |
| Assets | Quality thumbnail, reasonable file sizes |
Templates that check all these boxes typically pass review on the first submission. Addressing these from the start saves you revision cycles.