Accessibility and Color Schemes

September 10, 2026

Color Blindness


  • Color Vision Deficiency (CVD) or color blindness affects 8 percent of men and 1 in 200 women
  • There are different types of CVD but most common is red-green color blindness
  • Therefore, don’t include red and green in the same chart!
  • Look for color blind safe palettes

Color Should Carry Information


  • Color is a third dimension of your data, not decoration
  • If your x-axis already shows the categories, coloring by that same variable adds nothing
  • In the line chart below, color is doing real work–it is the only thing telling the three countries apart
  • That is exactly why the color scheme has to be readable for everyone

Last Week’s Line Chart

Last Week’s Line Chart


Create last week’s line chart and save it as an object…

dem_waves_ctrs <- read_csv("data/dem_waves_ctrs.csv")

dem_waves_chart <- ggplot(dem_waves_ctrs, aes(x = year, y = polyarchy, color = country)) +
  geom_line(linewidth = 1) + # our geom is a line with a width of 1
  labs(
    x = "Year",
    y = "Polyarchy Score",
    title = 'Democracy in countries representing three different "waves"',
    caption = "Source: V-Dem Institute",
    color = "Country" # make title of legend to upper case
  )

Checking Your Colors

Checking Your Colors


Call cvdPlot() from the colorBlindness package. CVD stands for “color vision deficiency.”

library(colorBlindness)

cvdPlot(dem_waves_chart)


Click on the little image in the plot pane to expand your view…

Your Turn!


  • Take your dem_waves_chart object and run cvdPlot() on it
  • Expand the window and have a good look
  • Which group would have the toughest time reading this graph?

Colorblind-Safe Palettes


Three ready-made options. All work the same way–add a scale_color_* (or scale_fill_*) to your plot:

  • Viridis – built into ggplot2, works for discrete and continuous data, and stays readable in grayscale
  • ColorBrewer – also built in, but only some of its palettes are safe; use the selector tool and check the “colorblind safe” box
  • paletteer – thousands of palettes through one function, including the classic Okabe-Ito set

Viridis

dem_waves_chart + scale_color_viridis_d()

ColorBrewer

Our countries are unordered categories, so we want a qualitative palette. Dark2 is the one that holds up best under CVD.

dem_waves_chart + scale_color_brewer(palette = "Dark2")

Paletteer

paletteer reaches palettes from all over the R ecosystem with one function. Here is Okabe-Ito, designed specifically for red-green CVD…

library(paletteer)

dem_waves_chart + scale_color_paletteer_d("colorblindr::OkabeIto")

Not Every Palette Is Safe

Picking a palette is not the same as picking an accessible one…

dem_waves_chart + scale_color_brewer(palette = "RdYlGn")

Not Every Palette Is Safe

Japan and the United States collapse into the same color–always check!

cvdPlot(dem_waves_bare + scale_color_brewer(palette = "RdYlGn"))

Build Your Own

Build Your Own


A palette is really just a vector of colors. These are the Okabe-Ito hex codes–apply any vector you like with scale_color_manual()

cb_palette <- c("#E69F00", "#56B4E9", "#009E73", "#F0E442", "#0072B2", "#D55E00", "#CC79A7")

dem_waves_chart + scale_color_manual(values = cb_palette)

Ask Your AI


When in doubt, paste your chart image–or just your hex codes–into an AI assistant and ask:

“Is this color scheme readable for someone with deuteranopia or protanopia? If not, suggest an accessible alternative and give me the ggplot2 code.”


Good for: spotting risky combinations, suggesting a better palette, writing the scale_color_* call for you.

Keep in mind: the AI is eyeballing your chart. cvdPlot() actually simulates the deficiency. Use the AI to pick a palette, then verify with cvdPlot().

Your Turn!

  • Go to Module 2.2 on the course website
  • Do the setup steps and rebuild the line chart
  • Try scale_color_viridis_d(), then scale_color_brewer(palette = "Dark2")
  • Run cvdPlot() on each one–which holds up best?
  • Then build your own: paste in the Okabe-Ito codes and change one hex code. Does it still pass?
  • Time permitting, try a coolors palette or GW colors

Fill vs. Color


Use fill (fill = or scale_fill_*) for the inside of shapes:

  • Bar charts, box plots, histograms


Use color (color = or scale_color_*) for points, lines, and text:

  • Scatter plots, line charts, text elements


Every palette we just saw has both versions, e.g. scale_fill_viridis_d() and scale_color_viridis_d().

Scaling for Scatter Plots

Scaling for Scatter Plots


wealth_flfp <- ggplot(flfp_gdp, aes(x = gdp_pc, y = flfp)) +
  geom_point(aes(color = region)) + # color points by region
  geom_smooth(method = "loess", linewidth = 1) +  # make the line a loess curve
  scale_x_log10(labels = scales::label_dollar()) + # stretch axis, add '$' format
  scale_y_continuous(labels = scales::label_percent(scale = 1)) + # add % label
  labs(
    x= "GDP per Capita", # x-axis title
    y = "Female Labor Force Participation", # y-axis title
    title = "Wealth and female labor force participation", # plot title
    caption = "Source: World Bank Development Indicators", # caption
    color = "Region" # legend title
    )

wealth_flfp + scale_color_viridis_d(option = "plasma")

Tuning the Scale

Use end to darken the colors. direction = -1 flips the scale.

wealth_flfp + scale_color_viridis_d(option = "plasma", end = .7)

Your Turn!


  • Try using one of the color schemes on the scatter plot
  • Use scale_color_ instead of scale_fill_
  • Play around with the end and direction arguments in viridis
  • A full list of viridis schemes is here
  • For ColorBrewer, check out this selector tool
  • For paletteer, check out this paletteer gallery