TSX Component Development Guide

This guide provides comprehensive standards for creating custom TSX components for ColivingLiguria pages, ensuring consistency, maintainability, and aesthetic excellence.

IMPORTANT

Read this document before creating any new custom page component

This guide is based on proven patterns from NewHome.tsx and ElectricalSystem.tsx. Following these standards ensures your pages maintain the site’s visual identity and professional quality.


Table of Contents

  1. Color Palette & Theme
  2. Component Structure
  3. Styling Conventions
  4. Common Patterns
  5. Responsive Design
  6. Accessibility
  7. Code Examples
  8. Page Layout Configuration

Page Layout Configuration

IMPORTANT

No Backlinks or Graph View on TSX Pages

Custom TSX pages (like the Homepage) are designed to be standalone experiences. You MUST disable the standard Quartz sidebar features (Backlinks, Graph View, Explorer) for these pages to prevent clutter and maintain visual focus.

How to disable in quartz.layout.tsx: Ensure your custom page’s layout configuration excludes Component.Backlinks(), Component.Graph(), and Component.Explorer().

Color Palette & Theme

Core Color Variables

Always use CSS variables - never hardcode colors. Our palette is defined in quartz.config.ts and available via CSS variables:

// Light Mode Colors
--light: "#F7F5F3"        // Warm Alabaster (backgrounds)
--lightgray: "#E5E0D8"    // Light Sand (secondary backgrounds)
--gray: "#948C84"         // Stone (muted text, borders)
--darkgray: "#4E4B42"     // Bark (secondary text)
--dark: "#2C2A24"         // Deep Peat (primary text)
--secondary: "#4A6741"    // Deep Moss Green (CTAs, highlights)
--tertiary: "#A67B5B"     // Raw Sienna/Clay (accents)
 
// Dark Mode Colors (auto-transforms)
--light: "#0F0E0B"        // Deep Earth Black
--lightgray: "#26231E"    // Dark Soil
--gray: "#857F75"         // Warm Stone
--darkgray: "#D6D1C9"     // Bone/Light Sand
--dark: "#F0ECE6"         // Off-white
--secondary: "#8FA876"    // Light Moss/Sage
--tertiary: "#D4A373"     // Buff/Sandstone

Usage Examples

// ✅ CORRECT - Using CSS variables
<div style="background: var(--lightgray); color: var(--dark);">
    <h2 style="color: var(--secondary);">Title</h2>
</div>
 
// ❌ WRONG - Hardcoded colors
<div style="background: #E5E0D8; color: #2C2A24;">
    <h2 style="color: #4A6741;">Title</h2>
</div>

Color Application Rules

ElementColor VariableUsage
Page Backgroundvar(--light)Main page background
Section Background (Alt)var(--lightgray)Alternating sections for visual rhythm
Cards/Boxeswhite in light mode, var(--lightgray) in darkElevated content containers
Primary Textvar(--dark)Headings, important text
Secondary Textvar(--gray)Body text, descriptions
Bordersvar(--lightgray)Subtle borders (2px max)
CTAs/Buttonsvar(--secondary) backgroundPrimary action buttons
Highlightsvar(--secondary) or var(--tertiary)Emphasis, badges, icons

TIP

Dark Mode Compatibility

Never use pure white or #FFFFFF for backgrounds - use var(--light). This ensures proper dark mode transformation.


Component Structure

File Organization

Every custom page component requires two files:

quartz/
├── components/
│   ├── YourComponent.tsx          // Component logic
│   └── styles/
│       └── yourcomponent.scss     // Component styles

Basic Component Template

import { QuartzComponent, QuartzComponentConstructor, QuartzComponentProps } from "./types"
import style from "./styles/yourcomponent.scss"
 
export default (() => {
    const YourComponent: QuartzComponent = ({ displayClass }: QuartzComponentProps) => {
        return (
            <div class={`your-component-page ${displayClass ?? ""}`}>
                {/* Content goes here */}
            </div>
        )
    }
 
    YourComponent.css = style
    return YourComponent
}) satisfies QuartzComponentConstructor

Registering the Component

  1. Add to components/index.ts:
// Import
import { default as YourComponent } from "./YourComponent"
 
// Export
export {
  // ... other exports
  YourComponent,
}
  1. Create content routing page:
---
title: Your Page Title
---

The component is automatically rendered based on matching routes.


Styling Conventions

SCSS Best Practices

Use Nested Selectors

.your-component-page {
  width: 100%;
  max-width: 100%;
  margin: 0;
  padding: 0;
 
  section {
    padding: 4rem 2rem;
    max-width: 1200px;
    margin: 0 auto;
 
    @media (max-width: 768px) {
      padding: 2rem 1rem;
    }
  }
}

Consistent Spacing Scale

Use multiples of 0.5rem for consistent spacing:

// Spacing scale
// 0.5rem, 1rem, 1.5rem, 2rem, 2.5rem, 3rem, 4rem, 6rem
 
.card {
  padding: 2rem;           // Standard card padding
  margin-bottom: 2rem;     // Standard vertical spacing
  gap: 1.5rem;            // Grid/flex gaps
}
 
.section {
  padding: 4rem 2rem;      // Section vertical/horizontal padding
}

Typography Hierarchy

h1 {
  font-size: 3rem;             // Hero titles
  line-height: 1.2;
  font-weight: bold;
 
  @media (max-width: 768px) {
    font-size: 2rem;
  }
}
 
h2 {
  font-size: 2.5rem;           // Section titles
  margin: 0 0 1rem 0;
 
  @media (max-width: 768px) {
    font-size: 2rem;
  }
}
 
h3 {
  font-size: 1.8rem;           // Subsection titles
  margin: 0 0 1rem 0;
}
 
p {
  font-size: 1rem;             // Body text
  line-height: 1.7;            // Comfortable reading
  color: var(--gray);
}

Border Radius Standards

// Border radius scale
border-radius: 8px;      // Small elements (badges, tags)
border-radius: 12px;     // Medium elements (small cards)
border-radius: 16px;     // Standard cards
border-radius: 24px;     // Large cards, sections
border-radius: 50px;     // Pills, buttons

Box Shadow Standards

// Subtle elevation
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.05);
 
// Medium elevation
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.08);
 
// Strong elevation (cards)
box-shadow: 0 8px 24px rgba(0, 0, 0, 0.1);
 
// Colored shadows (use sparingly)
box-shadow: 0 8px 24px rgba(74, 103, 65, 0.15);  // Secondary color shadow

Common Patterns

Hero Section

<section class="hero">
    <div class="hero-content">
        <div class="hero-badge">✨ Featured Project</div>
        <h1>Your Amazing Title</h1>
        <p class="hero-subtitle">
            Compelling subtitle that explains what this page is about.
        </p>
        <div class="cta-group">
            <a href="/link" class="cta-button primary">Primary Action</a>
            <a href="/link" class="cta-button secondary">Secondary Action</a>
        </div>
    </div>
</section>

SCSS:

.hero {
  text-align: center;
  padding: 6rem 2rem 4rem;
  background: linear-gradient(135deg, var(--lightgray) 0%, var(--light) 100%);
 
  .hero-badge {
    display: inline-block;
    background: var(--secondary);
    color: white;
    padding: 0.5rem 1.5rem;
    border-radius: 50px;
    font-size: 0.9rem;
    font-weight: 600;
    margin-bottom: 1.5rem;
  }
 
  h1 {
    font-size: 3rem;
    color: var(--dark);
    margin: 0 0 1rem 0;
    line-height: 1.2;
  }
 
  .hero-subtitle {
    font-size: 1.2rem;
    color: var(--gray);
    max-width: 800px;
    margin: 0 auto 2rem;
    line-height: 1.7;
  }
 
  .cta-group {
    display: flex;
    gap: 1rem;
    justify-content: center;
    flex-wrap: wrap;
  }
 
  .cta-button {
    padding: 1rem 2rem;
    border-radius: 50px;
    text-decoration: none;
    font-weight: 600;
    transition: transform 0.2s, box-shadow 0.2s;
 
    &:hover {
      transform: translateY(-2px);
      box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
    }
 
    &.primary {
      background: var(--secondary);
      color: white;
    }
 
    &.secondary {
      background: white;
      color: var(--secondary);
      border: 2px solid var(--secondary);
    }
  }
}

Grid Layouts

<section class="features-section">
    <h2 class="section-title">Key Features</h2>
    <div class="features-grid">
        <div class="feature-card">
            <div class="feature-icon">⚡</div>
            <h3>Feature Title</h3>
            <p>Feature description goes here.</p>
        </div>
        {/* Repeat for each feature */}
    </div>
</section>

SCSS:

.features-section {
  .section-title {
    text-align: center;
    font-size: 2.5rem;
    color: var(--dark);
    margin: 0 0 3rem 0;
  }
 
  .features-grid {
    display: grid;
    grid-template-columns: repeat(3, 1fr);
    gap: 2rem;
 
    @media (max-width: 768px) {
      grid-template-columns: 1fr;
    }
 
    .feature-card {
      background: white;
      border-radius: 16px;
      padding: 2rem;
      border: 2px solid var(--lightgray);
      transition: transform 0.3s, box-shadow 0.3s;
 
      &:hover {
        transform: translateY(-5px);
        box-shadow: 0 8px 24px rgba(0, 0, 0, 0.1);
      }
 
      .feature-icon {
        font-size: 3rem;
        margin-bottom: 1rem;
      }
 
      h3 {
        color: var(--dark);
        margin: 0 0 1rem 0;
        font-size: 1.5rem;
      }
 
      p {
        color: var(--gray);
        line-height: 1.6;
        margin: 0;
      }
    }
  }
}

Highlighted Info Boxes

<div class="info-box">
    <div class="info-header">
        <div class="info-icon">💡</div>
        <h3>Important Information</h3>
    </div>
    <p>Your informational content here.</p>
</div>

SCSS:

.info-box {
  background: var(--lightgray);
  border-radius: 16px;
  padding: 2rem;
  border-left: 4px solid var(--secondary);
  margin: 2rem 0;
 
  .info-header {
    display: flex;
    align-items: center;
    gap: 1rem;
    margin-bottom: 1rem;
 
    .info-icon {
      font-size: 2rem;
    }
 
    h3 {
      margin: 0;
      color: var(--dark);
    }
  }
 
  p {
    margin: 0;
    color: var(--gray);
    line-height: 1.7;
  }
}

Alternating Background Sections

<section class="section-light">
    {/* Content */}
</section>
 
<section class="section-gray">
    {/* Content */}
</section>
 
<section class="section-light">
    {/* Content */}
</section>

SCSS:

.section-light {
  background: var(--light);
  padding: 4rem 2rem;
}
 
.section-gray {
  background: var(--lightgray);
  padding: 4rem 2rem;
}

Responsive Design

Breakpoints

Use these standard breakpoints:

// Mobile
@media (max-width: 480px) {
  // Stacked layouts, larger touch targets
}
 
// Tablet
@media (max-width: 768px) {
  // Most responsive adjustments happen here
  grid-template-columns: 1fr;  // Single column grids
  padding: 2rem 1rem;          // Reduced padding
  font-size: 2rem;             // Smaller headings
}
 
// Desktop
@media (min-width: 769px) {
  // Default desktop styles
}

Mobile-First Pattern

// Start with mobile styles
.card {
  padding: 1rem;
  font-size: 1rem;
}
 
// Add desktop enhancements
@media (min-width: 769px) {
  .card {
    padding: 2rem;
    font-size: 1.1rem;
  }
}

Accessibility

Semantic HTML

// ✅ CORRECT - Semantic structure
<section class="features">
    <h2>Features</h2>
    <article class="feature-card">
        <h3>Feature Title</h3>
        <p>Description</p>
    </article>
</section>
 
// ❌ WRONG - Generic divs everywhere
<div class="features">
    <div>Features</div>
    <div class="feature-card">
        <div>Feature Title</div>
        <div>Description</div>
    </div>
</div>
// ✅ CORRECT - Descriptive link text
<a href="/properties/casa-del-forno">Explore Casa del Forno →</a>
 
// ❌ WRONG - Generic "click here"
<a href="/properties/casa-del-forno">Click here</a>

Color Contrast

Ensure minimum 4.5:1 contrast ratio for body text:

// ✅ CORRECT - High contrast
color: var(--dark);           // #2C2A24 on var(--light) #F7F5F3 = 11.8:1
background: var(--light);
 
// ⚠️ CHECK - Medium contrast (still acceptable)
color: var(--gray);           // #948C84 on var(--light) #F7F5F3 = 4.8:1
background: var(--light);
 
// ❌ WRONG - Low contrast
color: var(--lightgray);      // Too low contrast
background: var(--light);

Code Examples

Complete Mini Component

// YourComponent.tsx
import { QuartzComponent, QuartzComponentConstructor, QuartzComponentProps } from "./types"
import style from "./styles/yourcomponent.scss"
 
export default (() => {
    const YourComponent: QuartzComponent = ({ displayClass }: QuartzComponentProps) => {
        return (
            <div class={`your-component-page ${displayClass ?? ""}`}>
                
                {/* Hero */}
                <section class="hero">
                    <div class="hero-content">
                        <h1>Welcome to Our Page</h1>
                        <p class="subtitle">Discover amazing things here.</p>
                    </div>
                </section>
 
                {/* Features */}
                <section class="features">
                    <h2 class="section-title">What We Offer</h2>
                    <div class="features-grid">
                        <div class="feature-card">
                            <div class="icon">🎯</div>
                            <h3>Precision</h3>
                            <p>Carefully crafted experiences.</p>
                        </div>
                        <div class="feature-card">
                            <div class="icon">⚡</div>
                            <h3>Speed</h3>
                            <p>Lightning-fast performance.</p>
                        </div>
                        <div class="feature-card">
                            <div class="icon">🛡️</div>
                            <h3>Security</h3>
                            <p>Your safety is our priority.</p>
                        </div>
                    </div>
                </section>
 
            </div>
        )
    }
 
    YourComponent.css = style
    return YourComponent
}) satisfies QuartzComponentConstructor
// yourcomponent.scss
@use "../../styles/variables.scss" as *;
 
.your-component-page {
  width: 100%;
  max-width: 100%;
  margin: 0;
  padding: 0;
 
  section {
    padding: 4rem 2rem;
    max-width: 1200px;
    margin: 0 auto;
 
    @media (max-width: 768px) {
      padding: 2rem 1rem;
    }
  }
 
  // Hero
  .hero {
    text-align: center;
    background: linear-gradient(135deg, var(--lightgray) 0%, var(--light) 100%);
 
    h1 {
      font-size: 3rem;
      color: var(--dark);
      margin: 0 0 1rem 0;
 
      @media (max-width: 768px) {
        font-size: 2rem;
      }
    }
 
    .subtitle {
      font-size: 1.2rem;
      color: var(--gray);
      max-width: 600px;
      margin: 0 auto;
    }
  }
 
  // Features
  .features {
    background: var(--light);
 
    .section-title {
      text-align: center;
      font-size: 2.5rem;
      color: var(--dark);
      margin: 0 0 3rem 0;
    }
 
    .features-grid {
      display: grid;
      grid-template-columns: repeat(3, 1fr);
      gap: 2rem;
 
      @media (max-width: 768px) {
        grid-template-columns: 1fr;
      }
 
      .feature-card {
        background: white;
        border-radius: 16px;
        padding: 2rem;
        text-align: center;
        border: 2px solid var(--lightgray);
 
        .icon {
          font-size: 3rem;
          margin-bottom: 1rem;
        }
 
        h3 {
          color: var(--dark);
          margin: 0 0 0.5rem 0;
        }
 
        p {
          color: var(--gray);
          margin: 0;
        }
      }
    }
  }
}

Common Pitfalls & Troubleshooting

Problem: Component Created But Page Shows Only Title

Symptom: Your TSX component exists, builds successfully, but the page only shows the title with empty content below.

Cause: Component not registered in quartz.layout.tsx routing configuration.

Solution: Add your component to the layout configuration file.

Step-by-Step Fix

  1. Open quartz.layout.tsx in the project root

  2. Add ConditionalRender for your component in the beforeBody array:

Component.ConditionalRender({
  component: Component.YourComponent(),
  condition: (page) => page.fileData.slug?.toLowerCase().endsWith("your-page-slug") ?? false,
}),
  1. Add exclusions for standard page elements (ArticleTitle, ContentMeta, TagList):
// In ArticleTitle condition (around line 49)
slug.endsWith("your-page-slug")
 
// In ContentMeta condition (around line 77)  
slug.endsWith("your-page-slug")
 
// In TagList condition (around line 95)
slug.endsWith("your-page-slug")
  1. Complete example:
// In quartz.layout.tsx, around line 48
Component.ConditionalRender({
  component: Component.ArticleTitle(),
  condition: (page) => {
    const slug = page.fileData.slug?.toLowerCase() ?? ""
    return !(
      slug === "index" ||
      slug.endsWith("digital-nomad-offer") ||
      slug.endsWith("volunteer-offer") ||
      slug.endsWith("your-page-slug")  // ADD THIS
    )
  },
}),
 
// Later in beforeBody array, around line 140
Component.ConditionalRender({
  component: Component.YourComponent(),
  condition: (page) => page.fileData.slug?.toLowerCase().endsWith("your-page-slug") ?? false,
}),
  1. Rebuild: npx quartz build

Why This Happens

Quartz uses conditional rendering to determine which components display on which pages. Without a ConditionalRender entry, your component never gets invoked, even though it exists and is imported.

The exclusions (ArticleTitle, etc.) prevent standard page chrome from appearing on your custom component pages, giving you full control over the layout.


Checklist Before Publishing

Use this checklist every time you create a new page component:

  • Colors: All colors use CSS variables (no hardcoded hex)
  • Responsive: Tested on mobile (< 768px) and desktop
  • Spacing: Uses consistent spacing scale (0.5rem multiples)
  • Typography: Proper hierarchy (h1 > h2 > h3 > p)
  • Dark Mode: No pure white backgrounds
  • Accessibility: Semantic HTML, descriptive links, good contrast
  • Registered: Component added to components/index.ts
  • Styled: SCSS file created in styles/ folder
  • Tested: Build completes without errors (npx quartz build)

Quick Reference

CSS Variables Cheatsheet

// Backgrounds
var(--light)         // Main page background
var(--lightgray)     // Alt sections
white                // Elevated cards (light mode)
 
// Text
var(--dark)          // Primary headings
var(--darkgray)      // Secondary headings
var(--gray)          // Body text
 
// Interactive
var(--secondary)     // Buttons, CTAs, links
var(--tertiary)      // Accents, highlights
 
// Borders
var(--lightgray)     // Most borders (2px max)
var(--secondary)     // Emphasized borders (left border, etc.)

Common Class Patterns

.section-title       // Centered, 2.5rem section titles
.hero               // Full-width hero sections
.cta-button         // Call-to-action buttons
.feature-card       // Grid item cards
.info-box           // Highlighted information boxes

Resources

  • Reference Components: NewHome.tsx, ElectricalSystem.tsx
  • Color Palette: quartz.config.ts
  • Markdown Guide: Markdown Style Guide.md
  • Build Command: npx quartz build

Remember: Consistency is key. When in doubt, reference existing components and follow established patterns.