# High contrast modes

Respect Increase Contrast with one rule for every family: text and borders two stops stronger.

Apple's high contrast mode is the Increase Contrast setting in Accessibility. Tokens don't follow it on their own, so your view reads it and asks for stronger colors: text and borders two stops further from the background, in every family. Apple's [Human Interface Guidelines](https://developer.apple.com/design/human-interface-guidelines/color) ask custom colors to offer an increased contrast option.

## Strengthen colors when the setting is on

A trip card that follows the setting, shown off and then on:

```swift
extension Color {
    func strengthened(by stops: Int, when contrast: ColorSchemeContrast) -> Color {
        contrast == .increased ? strengthen(by: stops) : self
    }
}

struct TripCard: View {
    @Environment(\.colorSchemeContrast) private var contrast
    let theme: ProTheme

    var body: some View {
        VStack(alignment: .leading, spacing: 4) {
            Text("Lisbon")
                .font(.headline)
                .foregroundStyle(theme.foregroundPrimary)
            Text("3 nights from October 12")   // _800 becomes _900 in light mode, _200 becomes _100 in dark
                .font(.subheadline)
                .foregroundStyle(theme.foregroundSecondary.strengthened(by: 2, when: contrast))
        }
        .padding()
        .border(theme.outlinePrimary.strengthened(by: 2, when: contrast))   // _600 and _350 become _700 and _250
        .padding()
        .background(theme.backgroundPrimary)
    }
}
```

What this draws, with `Color.proOrange`:

| Color                 | light mode                             | dark mode                              |
| --------------------- | -------------------------------------- | -------------------------------------- |
| `backgroundPrimary`   | color(display-p3 0.9936 0.9528 0.9271) | #301404                                |
| `outlinePrimary`      | #b35c24                                | color(display-p3 0.9658 0.5958 0.3524) |
| `foregroundPrimary`   | #301404                                | color(display-p3 0.9936 0.9528 0.9271) |
| `foregroundSecondary` | #6e3612                                | color(display-p3 0.978 0.79 0.6716)    |

What this draws, with `Color.proOrange`:

| Color               | light mode                             | dark mode                              |
| ------------------- | -------------------------------------- | -------------------------------------- |
| `backgroundPrimary` | color(display-p3 0.9936 0.9528 0.9271) | #301404                                |
| `_700/_250`         | #90491a                                | color(display-p3 0.9744 0.7286 0.573)  |
| `foregroundPrimary` | #301404                                | color(display-p3 0.9936 0.9528 0.9271) |
| `_900/_100`         | #4e250a                                | color(display-p3 0.9885 0.9007 0.8455) |

SwiftUI reports the setting as `colorSchemeContrast`. [`strengthen(by:)`](https://colortokenskit.com/api/strengthen/index.md) moves a color away from the background, darker in light mode and lighter in dark, and a token lands on an exact stop.

## Contrast with the setting on

Tertiary text lands where secondary text was. The border already passes the 3:1 WCAG asks of [borders people need to see](https://colortokenskit.com/advanced/color-theory/index.md#borders-people-need-to-see) with the setting off, and with it on it measures 6.10:1 in light mode and 10.20:1 in dark. Leave `foregroundPrimary` as it is: it's already at the end of the ramp.

In dark mode, strengthened tertiary text also passes the Lc 75 that [APCA](https://colortokenskit.com/reference/glossary/index.md#apca) asks for body text.

## UIKit and AppKit

In UIKit, read the `accessibilityContrast` trait in a dynamic `UIColor` built from the token's stops. `UIColor` takes a SwiftUI `Color`, not a stop, so the stop goes through `toColor()`:

```swift
import UIKit

// foregroundSecondary, two stops stronger when Increase Contrast is on
let datesColor = UIColor { traits in
    let increased = traits.accessibilityContrast == .high
    let stop = traits.userInterfaceStyle == .dark
        ? (increased ? theme._100 : theme._200)
        : (increased ? theme._900 : theme._800)
    return UIColor(stop.toColor())
}
```

Set layer colors again when the traits change, as the [UIKit](https://colortokenskit.com/platforms/uikit/index.md) and [AppKit](https://colortokenskit.com/platforms/appkit/index.md) pages show.

AppKit reports it through the appearance:

```swift
import AppKit

// foregroundSecondary, two stops stronger when Increase Contrast is on
let datesColor = NSColor(name: nil) { appearance in
    let stop = switch appearance.bestMatch(from: [.aqua, .darkAqua, .accessibilityHighContrastAqua, .accessibilityHighContrastDarkAqua]) {
    case .darkAqua: theme._200
    case .accessibilityHighContrastAqua: theme._900
    case .accessibilityHighContrastDarkAqua: theme._100
    default: theme._800
    }
    return NSColor(stop.toColor())
}
```

This follows Apple's [NSAppearance documentation](https://developer.apple.com/documentation/appkit/nsappearance). We haven't tested it with the setting on.

## Troubleshooting

### Setting the contrast in a preview doesn't compile

`colorSchemeContrast` is read-only, because SwiftUI takes it from the system. To preview the stronger colors, pass `.increased` to the helper, or use Xcode's Environment Overrides while your app runs.

### Colors don't change when the setting is on

A color stored once, in a `static let` for example, can't see the environment. Read `colorSchemeContrast` in the view, as `TripCard` does.

## See also

- [How accessible is it?](https://colortokenskit.com/basics/how-accessible/index.md): What the palette guarantees for contrast in every hue and mode, and what you still need to check.
- [.contrastRatio(to:method:)](https://colortokenskit.com/api/contrast-ratio/index.md): Check WCAG 2 and APCA contrast in Swift, and keep your color pairs passing with a unit test.
- [Managing dark mode](https://colortokenskit.com/getting-started/dark-mode/index.md): Get dark mode without extra code: tokens switch stops for you, and your own colors can too.
- [Building for interaction states](https://colortokenskit.com/getting-started/interaction-states/index.md): Give buttons and rows hover, pressed, selected and disabled colors that stay readable in both modes.

---

From ColorTokensKit, by Penguin Design Ventures: https://colortokenskit.com/advanced/high-contrast/ (updated September 26, 2026).
