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.
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.
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>
)
}
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]
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
}
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 },
],
}
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>
)
}
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.