Building a CAGED System Visualization: Guitar Theory Interactive Guide

Building a CAGED System Visualization: Guitar Theory Interactive Guide

March 19, 2025

5 min read

Building a CAGED System Visualization: Guitar Theory Interactive Guide

Introduction

This is the fourth part of our series where we build an app for learning guitar theory. We've already covered full, pentatonic, and blues scales. Today, we'll explore the CAGED system, starting with the foundation of five open chords that serve as building blocks for this approach. You can find all the source code in the GitHub repository.

CAGED chords
CAGED chords

The CAGED system encompasses many concepts, but we'll tackle it incrementally. In this post, we'll focus on creating a new page that displays the five fundamental open chords—C, A, G, E, and D—which form the backbone of this system. Mastering these shapes is essential before advancing to more complex CAGED applications in future posts.

Building the CAGED Page Component

import { HStack, VStack } from "@lib/ui/css/stack"
import { PageContainer } from "../layout/PageContainer"
import { CagedPageTitle } from "./CagedPageTitle"
import { cagedChords } from "@product/core/chords/caged"
import { CagedChordItem } from "./CagedChordItem"

export const CagedPage = () => {
  return (
    <PageContainer>
      <VStack gap={60}>
        <CagedPageTitle />
        <HStack
          fullWidth
          gap={60}
          alignItems="center"
          justifyContent="center"
          wrap="wrap"
        >
          {cagedChords.map((chord) => (
            <CagedChordItem key={chord} value={chord} />
          ))}
        </HStack>
      </VStack>
    </PageContainer>
  )
}

Defining Chord Types with TypeScript

By defining the cagedChords array with TypeScript's as const assertion, we achieve two benefits: we can map through the array to render each chord component, and we create a precise type definition that ensures type safety throughout our application.

export const cagedChords = ["c", "a", "g", "e", "d"] as const

export type CagedChord = (typeof cagedChords)[number]

Modeling Note Positions

To represent the precise location of notes on the guitar fretboard, we define a NotePosition type with two key properties: a zero-based string index and a fret number. The fret value uses a specific convention where -1 indicates an open string (no finger pressed), 0 represents the first fret, and so on.

export type NotePosition = {
  // 0-based index of the string
  string: number
  // -1 if the note is open
  // 0 if the note is on the 1st fret
  fret: number
}

Mapping Chord Finger Positions

For our chord visualization, we'll create a structured data model that precisely maps each CAGED chord to its fingerboard positions. Each chord is represented as an array of NotePosition objects, capturing the exact placement of fingers on the fretboard.

export const openCagedChords: Record<CagedChord, NotePosition[]> = {
  c: [
    { string: 0, fret: -1 },
    { string: 1, fret: 0 },
    { string: 2, fret: -1 },
    { string: 3, fret: 1 },
    { string: 4, fret: 2 },
  ],
  a: [
    { string: 0, fret: -1 },
    { string: 1, fret: 1 },
    { string: 2, fret: 1 },
    { string: 3, fret: 1 },
    { string: 4, fret: -1 },
  ],
  g: [
    { string: 0, fret: 2 },
    { string: 1, fret: -1 },
    { string: 2, fret: -1 },
    { string: 3, fret: -1 },
    { string: 4, fret: 1 },
    { string: 5, fret: 2 },
  ],
  e: [
    { string: 0, fret: -1 },
    { string: 1, fret: -1 },
    { string: 2, fret: 0 },
    { string: 3, fret: 1 },
    { string: 4, fret: 1 },
    { string: 5, fret: -1 },
  ],
  d: [
    { string: 0, fret: 1 },
    { string: 1, fret: 2 },
    { string: 2, fret: 1 },
    { string: 3, fret: -1 },
  ],
}

Rendering Chords on the Fretboard

To render each chord on the fretboard, our CagedChordItem component accepts a CagedChord value and visually maps its positions using the Fretboard and Note components. The component accesses the corresponding finger positions from our openCagedChords data structure and places each note at its precise location. For better visual hierarchy, we identify the bass note (on the lowest-numbered string) and apply the "primary" styling through the kind prop. If you want to explore the underlying fretboard visualization system, check out the first post in this series.

import { ValueProp } from "@lib/ui/props"
import { CagedChord, openCagedChords } from "@product/core/chords/caged"
import { vStack } from "@lib/ui/css/stack"
import { Text } from "@lib/ui/text"
import { Fretboard } from "../guitar/fretboard/Fretboard"
import { Note } from "../guitar/fretboard/Note"
import { cagedConfig } from "./config"
import styled from "styled-components"

const Container = styled.div`
  ${vStack({
    gap: 40,
  })}

  min-width: 320px;
  max-width: 400px;
`

export const CagedChordItem = ({ value }: ValueProp<CagedChord>) => {
  const positions = openCagedChords[value]

  const lowestBassString = Math.max(
    ...positions.map((position) => position.string),
  )

  return (
    <Container>
      <Text centerHorizontally color="contrast" as="h3" weight="700" size={18}>
        Open {value.toUpperCase()} chord
      </Text>
      <Fretboard visibleFrets={cagedConfig.visibleFrets}>
        {positions.map((position) => (
          <Note
            key={`${position.string}-${position.fret}`}
            string={position.string}
            fret={position.fret}
            kind={position.string === lowestBassString ? "primary" : "regular"}
          />
        ))}
      </Fretboard>
    </Container>
  )
}

Conclusion

With this interactive visualization of the five CAGED chords, you now have a solid foundation for understanding this powerful framework. In the next post, we'll explore how these shapes connect across the fretboard to unlock the full potential of the CAGED system.