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.tsxandElectricalSystem.tsx. Following these standards ensures your pages maintain the site’s visual identity and professional quality.
Table of Contents
- Color Palette & Theme
- Component Structure
- Styling Conventions
- Common Patterns
- Responsive Design
- Accessibility
- Code Examples
- 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 excludesComponent.Backlinks(),Component.Graph(), andComponent.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/SandstoneUsage 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
| Element | Color Variable | Usage |
|---|---|---|
| Page Background | var(--light) | Main page background |
| Section Background (Alt) | var(--lightgray) | Alternating sections for visual rhythm |
| Cards/Boxes | white in light mode, var(--lightgray) in dark | Elevated content containers |
| Primary Text | var(--dark) | Headings, important text |
| Secondary Text | var(--gray) | Body text, descriptions |
| Borders | var(--lightgray) | Subtle borders (2px max) |
| CTAs/Buttons | var(--secondary) background | Primary action buttons |
| Highlights | var(--secondary) or var(--tertiary) | Emphasis, badges, icons |
TIP
Dark Mode Compatibility
Never use pure
whiteor#FFFFFFfor backgrounds - usevar(--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 QuartzComponentConstructorRegistering the Component
- Add to
components/index.ts:
// Import
import { default as YourComponent } from "./YourComponent"
// Export
export {
// ... other exports
YourComponent,
}- 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, buttonsBox 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 shadowCommon 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>Link Accessibility
// ✅ 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
-
Open
quartz.layout.tsxin the project root -
Add ConditionalRender for your component in the
beforeBodyarray:
Component.ConditionalRender({
component: Component.YourComponent(),
condition: (page) => page.fileData.slug?.toLowerCase().endsWith("your-page-slug") ?? false,
}),- 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")- 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,
}),- 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 boxesResources
- 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.