Rams MCP · The full engine, now in your coding agent
exyte on GitHub

exyte/popupview

Reviewed against Rams quality heuristics: accessibility, color, typography, spacing, components, motion, UX, and craft.

30 files reviewed·July 29, 2026

View on GitHub

Top fix

Replace hardcoded hex values with semantic color tokens

See the fix

Verdict

Solid popup configuration logic sits on top of a visual layer that ignores color tokens and Dynamic Type entirely. Six hardcoded watch colors and unscaled text mean the moment appearance modes or accessibility sizing matter, this breaks.

Files Rams reviewed

PopupExample/PopupExample/BGTapsExamplesView.swift

PopupExample/PopupExample/GithubExample/GithubExampleView.swift

PopupExample/PopupExample/MiscExamplesView.swift

PopupExample/PopupExample/PositionExamplesView.swift

PopupExample/PopupExample/ScrollExamplesView.swift

PopupExample/PopupWatchExample Watch App/ContentView.swift

Sources/PopupView/PopupBackgroundView.swift

PopupExample/PopupExample/GithubExample/MockPopupViews/ActionSheets.swift

PopupExample/PopupExample/GithubExample/MockPopupViews/FloatsBig.swift

PopupExample/PopupExample/GithubExample/MockPopupViews/FloatsSmall.swift

PopupExample/PopupExample/GithubExample/MockPopupViews/InputSheets.swift

PopupExample/PopupExample/GithubExample/MockPopupViews/Popups.swift

PopupExample/PopupExample/GithubExample/MockPopupViews/Toasts.swift

PopupExample/PopupExample/GithubExample/PopupTypeIcons.swift

PopupExample/PopupExample/GithubExample/PopupTypesButtonsList.swift

PopupExample/PopupExample/PopupExampleApp.swift

PopupExample/PopupExample/Utils/ButtonsMatrix.swift

PopupExample/PopupExample/Utils/ButtonsSwitcher.swift

PopupExample/PopupExample/Utils/Utils.swift

Sources/PopupView/PopupBody.swift

Sources/PopupView/PopupModifier.swift

Sources/PopupView/PublicAPI.swift

Sources/PopupView/PublicModifiers.swift

Sources/PopupView/Utils/DragToDismissHelper.swift

Sources/PopupView/Utils/KeyboardHeightHelper.swift

Sources/PopupView/Utils/ScrollPopupModifier.swift

Sources/PopupView/Utils/TransparentNonAnimatableFullScreenModifier.swift

Sources/PopupView/Utils/Utils.swift

Sources/PopupView/Utils/WindowManager.swift

PopupExample/PopupWatchExample Watch App/PopupWatchExampleApp.swift

96/100

Color

2 serious
ColorSerious

PopupExample/PopupExample/MiscExamplesView.swift:26

Popup scrim uses a hardcoded black opacity instead of a token

The scroll popup's `customizeScroll` block sets `.backgroundColor(.black.opacity(0.4))` directly, while the rest of the screen uses a named asset color (`Color(.lightGrey)`) for its background.

Why it matters

A raw black-opacity literal can't be swapped centrally if the scrim needs to adapt for Dark Mode, Increase Contrast, or a future theme change, forcing a find-and-replace across every popup call site instead of one token update.

Fix

Define the scrim as a named color asset so appearance changes propagate from a single source instead of hardcoded literals.

.backgroundColor(.black.opacity(0.4))
.backgroundColor(Color(.scrim))
ColorSerious

PopupExample/PopupWatchExample Watch App/ContentView.swift:32

Six watch surface colors hardcoded as hex literals

`ContentView` defines `bgColor`, `popupColor`, `topToastColor`, `bottomToastColor`, `topFloatColor`, and `bottomFloatColor` all as `Color(hex: "...")` literals rather than asset-catalog or semantic colors.

Why it matters

None of these six colors can respond to Dark Mode or Increase Contrast, and any future palette change means hunting down six hex strings scattered through one file instead of editing one source of truth.

Fix

Move fixed hex values into named color assets so appearance mode and future palette edits update from a single place.

let bgColor = Color(hex: "e0fbfc")
let popupColor = Color(hex: "3d5a80")
let topToastColor = Color(hex: "293241")
let bottomToastColor = Color(hex: "98c1d9")
let topFloatColor = Color(hex: "293241")
let bottomFloatColor = Color(hex: "ee6c4d")
let bgColor = Color("BackgroundColor")
let popupColor = Color("PopupColor")
let topToastColor = Color("TopToastColor")
let bottomToastColor = Color("BottomToastColor")
let topFloatColor = Color("TopFloatColor")
let bottomFloatColor = Color("BottomFloatColor")
98/100

Accessibility

1 serious
AccessibilitySerious

PopupExample/PopupExample/MiscExamplesView.swift:51

Card title and detail text read as two separate VoiceOver stops

The `MiscPopupShowingButton` wraps Text(title) and Text(details) inside a Button's label ZStack with no accessibility grouping. VoiceOver will announce the title and the detail line as two discrete elements when swiping through the list, instead of one button with one clear label.

Why it matters

A VoiceOver user has to swipe twice and mentally reassemble the card's meaning before deciding whether to activate it, which slows navigation through every popup demo in the list.

Fix

Combine child text into a single accessibility element with one descriptive label per interactive card.

Button {
    show = true
} label: {
    ZStack(alignment: .leading) {
        RoundedRectangle(cornerRadius: 12)
            .fill(.white)

        VStack(alignment: .leading, spacing: 0) {
            Text(title)
                .font(.system(size: 18))

            Text(details)
                .font(.system(size: 13))
                .opacity(0.4)
        }
        .foregroundStyle(.black)
        .padding()
    }
}
Button {
    show = true
} label: {
    ZStack(alignment: .leading) {
        RoundedRectangle(cornerRadius: 12)
            .fill(.white)

        VStack(alignment: .leading, spacing: 0) {
            Text(title)
                .font(.system(size: 18))

            Text(details)
                .font(.system(size: 13))
                .opacity(0.4)
        }
        .foregroundStyle(.black)
        .padding()
    }
}
.accessibilityElement(children: .combine)
.accessibilityLabel("\(title). \(details)")

Typography

No issues found

Spacing

No issues found

Components

No issues found

Motion

No issues found

UX

No issues found

Craft

No issues found

Working well

  • The comment explaining why `.overlay` can't allow taps through while also detecting them for popup dismiss documents a real edge case instead of leaving future maintainers to rediscover it by trial and error.
  • `ExampleButton` wraps its tap target in a real SwiftUI Button with a visible Text label, which gives it correct accessibility and press-state feedback for free instead of a manually-gestured view.
  • The TextField("Leave a comment...") is given `.frame(height: 44)`, hitting the platform's minimum comfortable tap target so users with imprecise input still land taps reliably.

Scored July 29, 2026 with Rams Engine v0.0.3 · Engine changelog

This page is an automated design review of exyte/popupview’s UI code: 30 files read against 258 versioned rules covering accessibility, color, typography, spacing, components, UX, motion, and craft. The score is out of 100; any confirmed critical issue caps it at 59.

More design scores

Score your own repo.

Free on public repos, no account. The same engine that scored this page reads your UI code and mints a score page like this one.

Public repos only. The full engine reviews the UI code and mints a public score page — we email you the link too. Already-scored repos open instantly.

Or get a design review on every pull requestInstall Rams