Skip to content

Building Flexible Modules

Now let’s compose our CardTest component into a reusable flexible module. Flexible modules are section-level layouts that combine components, grid systems, and spacing to create complete page sections.


Create: src/flexible/modules/ModuleExample.astro

---
import CardTest from "@components/card/CardTest.astro";
import { spacingConverter, getLine } from "@utilities/astro";
const {
payload: {
spacing,
line,
key
},
language,
} = Astro.props;
const spaces = spacingConverter(spacing);
const lineClass = getLine(line);
---
<section class:list={[spaces, lineClass]} id={key}>
<div class="f--container">
<div class="f--row f--gap-a">
<!-- Header Section -->
<div class="f--col-12">
<h2 class="f--font-c u--font-semibold f--sp-d">
Welcome to Our Services
</h2>
<p class="f--font-g">
Discover how we can help transform your business with our innovative solutions.
</p>
</div>
<!-- Card 1 -->
<div class="f--col-6 f--col-tablets-12">
<CardTest
payload={{
language,
title: "Cloud Security",
subtitle: "Protect your data with enterprise-grade security solutions.",
media: {
type: "Image",
url: "/images/img-desktop_1.webp",
alt: "Cloud Security Icon",
},
link: {
label: "Learn More",
url: "/services/cloud-security",
target: "_self",
},
}}
/>
</div>
<!-- Card 2 -->
<div class="f--col-6 f--col-tablets-12">
<CardTest
payload={{
language,
title: "Data Analytics",
subtitle: "Make informed decisions with powerful data insights.",
media: {
type: "Image",
url: "/images/img-desktop_2.webp",
alt: "Data Analytics Icon",
},
link: {
label: "Explore",
url: "/services/data-analytics",
target: "_self",
},
}}
/>
</div>
</div>
</div>
</section>

<section class:list={[spaces, lineClass]} id={key}>
<div class="f--container">
<div class="f--row f--gap-a">
<!-- content -->
</div>
</div>
</section>

Hierarchy:

  • <section> - Module wrapper with spacing and line styling
  • .f--container - Max-width container (centers content)
  • .f--row - Grid row (flex container)
  • .f--col-* - Grid columns (responsive width)

📚 Learn more about Grid System in Chapter 2-4


const spaces = spacingConverter(spacing);
const lineClass = getLine(line);

spacingConverter generates spacing classes:

  • spacing: { top: 10, bottom: 7 }"u--pt-10 u--pb-7"
  • Uses the measure-based spacing scale (0, 1, 2, 3, 4, 5, 7, 8, 10, 15)

getLine generates divider line classes:

  • line: "top""u--line-top"
  • Options: "top", "bottom", "both", false

Spacing Example:

<!-- Module with top spacing of 80px and bottom spacing of 56px -->
payload={{
spacing: { top: 10, bottom: 7 },
line: "bottom",
key: "services-section"
}}

<div class="f--col-6 f--col-tablets-12">

Responsive Column Pattern:

  • .f--col-6 - 50% width on desktop (6 of 12 columns)
  • .f--col-tablets-12 - 100% width on tablets and below

Common Patterns:

Two Columns:

<div class="f--col-6 f--col-tablets-12">

Three Columns:

<div class="f--col-4 f--col-tablets-6 f--col-mobile-12">

Four Columns:

<div class="f--col-3 f--col-tabletl-6 f--col-tablets-12">

Full Width:

<div class="f--col-12">

<div class="f--row f--gap-a">

Gap Scale:

  • .f--gap-a - $measure * 5 (40px)
  • .f--gap-b - $measure * 4 (32px)
  • .f--gap-c - $measure * 3 (24px)
  • .f--gap-d - $measure * 2 (16px)

When to Use:

  • .f--gap-a - Large spacing between elements (default for sections)
  • .f--gap-b - Medium spacing
  • .f--gap-c - Small spacing
  • .f--gap-d - Minimal spacing

1. Use semantic section IDs

<section id={key}> <!-- Dynamic from CMS -->
<section id="services-overview"> <!-- Static/hardcoded -->

2. Keep modules flexible

  • Accept dynamic content via props
  • Support multiple configurations
  • Handle edge cases (0 items, 1 item, etc.)

3. Use grid for responsive layout

<div class="f--col-6 f--col-tablets-12">

4. Apply consistent spacing

<h2 class="f--sp-d">Title</h2> <!-- Spacing bottom -->

5. Maintain typography hierarchy

<h2 class="f--font-c u--font-semibold">Main Heading</h2>
<p class="f--font-g">Description text</p>

1. Don’t hardcode spacing - use utilities

<!-- ❌ Bad -->
<section style="padding: 80px 0 56px 0;">
<!-- ✅ Good -->
<section class:list={[spaces, lineClass]}>

2. Don’t skip responsive classes

<!-- ❌ Bad - Won't stack on mobile -->
<div class="f--col-6">
<!-- ✅ Good -->
<div class="f--col-6 f--col-tablets-12">

3. Don’t nest sections within sections

<!-- ❌ Bad -->
<section>
<section>
Content
</section>
</section>
<!-- ✅ Good -->
<section>
<div>
Content
</div>
</section>

4. Don’t forget semantic HTML

<!-- ❌ Bad -->
<div>
<div>Title</div>
<div>Description</div>
</div>
<!-- ✅ Good -->
<section>
<h2>Title</h2>
<p>Description</p>
</section>

For production modules receiving data from CMS, use this pattern:

---
const {
payload: {
spacing,
line,
key,
title, // ← Dynamic from CMS
subtitle, // ← Dynamic from CMS
cards, // ← Array of card data from CMS
},
language,
} = Astro.props;
const spaces = spacingConverter(spacing);
const lineClass = getLine(line);
---
<section class:list={[spaces, lineClass]} id={key}>
<div class="f--container">
<div class="f--row f--gap-a">
{title && (
<div class="f--col-12">
<h2 class="f--font-c u--font-semibold f--sp-d">{title}</h2>
{subtitle && <p class="f--font-g">{subtitle}</p>}
</div>
)}
{cards?.map((card) => (
<div class="f--col-6 f--col-tablets-12">
<CardTest
payload={{
language,
...card, // Spread card data from CMS
}}
/>
</div>
))}
</div>
</div>
</section>

<div class="f--row f--gap-a">
<!-- Large featured item -->
<div class="f--col-8 f--col-tablets-12">
<CardTest payload={featuredCard} />
</div>
<!-- Smaller sidebar -->
<div class="f--col-4 f--col-tablets-12">
<CardTest payload={sidebarCard} />
</div>
</div>
<div class="f--row f--gap-a">
<!-- Header -->
<div class="f--col-12">
<h2>Title</h2>
</div>
<!-- Three equal columns -->
<div class="f--col-4 f--col-tablets-12">
<CardTest payload={card1} />
</div>
<div class="f--col-4 f--col-tablets-12">
<CardTest payload={card2} />
</div>
<div class="f--col-4 f--col-tablets-12">
<CardTest payload={card3} />
</div>
</div>
<div class="f--row f--gap-a">
<div class="f--col-6 f--col-tablets-12">
<!-- Nested row inside column -->
<div class="f--row f--gap-c">
<div class="f--col-6">
<CardTest payload={card1} />
</div>
<div class="f--col-6">
<CardTest payload={card2} />
</div>
</div>
</div>
</div>

Created CardTest Component

  • Learned Astro component structure
  • Used Asset and Btn custom components
  • Followed BEM naming conventions
  • Implemented conditional rendering

Styled with SCSS

  • Used Foundation tokens (colors, typography, spacing)
  • Applied utility classes via @extend
  • Implemented responsive design
  • Added proper hover states

Added JavaScript Behavior

  • Built CustomAnimation class following Terra’s pattern
  • Used GSAP for smooth animations
  • Integrated ScrollTrigger for viewport detection
  • Created Handler for lifecycle management
  • Registered everything in the framework

Built ModuleExample

  • Composed components into a flexible module
  • Used the grid system for responsive layout
  • Applied spacing and gap utilities
  • Structured semantic HTML

1. Create Component (CardTest.astro)
2. Add SCSS Styles (_c--card-test.scss)
3. Register SCSS (style.scss)
4. Create Animation Class (CustomAnimation.js)
5. Create Handler (CustomAnimationHandler.js)
6. Register Animation Asset (extraAssets.js)
7. Register Handler (Main.js)
8. Allocate Instances (Project.js)
9. Build Module (ModuleExample.astro)
10. Test & Iterate

Component Development:

  • Props destructuring from payload
  • BEM naming convention (c--block__element)
  • Conditional rendering patterns
  • Component composition

SCSS Architecture:

  • Foundation token system
  • Utility class patterns
  • Responsive breakpoints
  • Hover state best practices

JavaScript Framework:

  • Terra’s four-pillar class structure
  • GSAP timeline management
  • ScrollTrigger integration
  • Handler lifecycle management
  • Manager instance tracking

Module Composition:

  • Grid system usage
  • Spacing utilities
  • Semantic HTML structure
  • Dynamic vs static data patterns

  1. 📖 Review Style Foundations in depth
  2. 🎨 Experiment with different color and typography combinations
  3. ✨ Try creating variations of CardTest with modifiers
  4. 📱 Test responsive behavior on different devices
  1. 🧩 Create more complex components (sliders, modals, accordions)
  2. 🎬 Build custom GSAP animations and transitions
  3. 🔄 Understand SWUP lifecycle in depth
  4. 📦 Explore existing modules for patterns
  1. ⚡ Optimize performance with Boostify
  2. 🏗️ Architect complex multi-component modules
  3. 🎯 Create reusable animation systems
  4. 🚀 Contribute to the framework architecture

Manual Testing:

  • Component renders on initial page load
  • Styles match design specifications
  • Responsive behavior works on all breakpoints
  • Animations trigger at correct scroll position
  • Hover states work correctly on desktop
  • No hover issues on touch devices
  • Component survives SWUP page transition
  • No console errors
  • No memory leaks (check Performance tab)
  • Accessibility (keyboard navigation, ARIA)
  • Works in all supported browsers

Performance Checklist:

  • Images optimized and lazy loaded
  • Animations run at 60fps
  • No layout shifts (CLS)
  • Fast initial render (LCP)
  • Proper code splitting implemented

Component: src/components/card/CardTest.astro
SCSS: src/scss/framework/components/card/_c--card-test.scss
SCSS Registration: src/scss/style.scss
Animation Class: src/scripts/module/CustomAnimation.js
Handler: src/scripts/handler/customAnimation/CustomAnimationHandler.js
Asset Definition: src/scripts/preload/extraAssets.js
Handler Registry: src/scripts/Main.js
Instance Pool: src/scripts/Project.js
Module: src/flexible/modules/ModuleExample.astro
// Layout
.f--container // Max-width container
.f--row // Flex row
.f--col-6 // 6 columns (50%)
.f--col-tablets-12 // 12 columns on tablets
// Spacing
.u--pt-10 // Padding top 80px
.u--pb-7 // Padding bottom 56px
.f--sp-d // Spacing bottom (gap)
// Typography
.f--font-c // Font size C (~32px)
.f--font-d // Font size D (~24px)
.f--font-g // Font size G (~16px)
.u--font-semibold // Font weight 600
// Colors
.f--color-a // Navy (primary)
.f--color-h // Gray (text)

You now have a complete understanding of the NETWRIX component-to-module workflow! 🎉

Remember the key principles:

  • Always use Foundation tokens before writing custom CSS
  • Follow Terra’s four-pillar class structure
  • Implement destroy methods for all interactive components
  • Test responsive behavior across all breakpoints
  • Use the grid system for consistent layouts

Keep building! The best way to master these patterns is through practice.

Happy coding! 🚀