exyte/popupview
Reviewed against Rams quality heuristics: accessibility, color, typography, spacing, components, motion, UX, and craft.
30 files reviewed·July 29, 2026
More findings
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
Color
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))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")Accessibility
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
Spacing
Components
Motion
UX
Craft
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.