
Highlight Sweep
From syxUI — written for both platforms, not translated between them.
A marker-pen highlight that grows behind a phrase as you read it.
Text Effects · SwiftUI · Flutter · iOS 17 · Flutter 3.27
- text
- highlight
- marker
- sweep
- emphasis
- editorial
- animation
The actual source
HighlightSweep.swift
// Highlight Sweep · syxUI · https://syxui.dev/components/highlight-sweep
// Free in any project. Keep this line and credit syxUI where a person can read it.
import SwiftUI
/// A marker-pen highlight that grows behind a phrase as you read it.
///
/// One `Text`, one band — no per-character work at all. The band composites
/// with `.multiply` so it tints whatever is behind the whole view like ink on
/// paper rather than sitting on top as a flat opaque block; `.compositingGroup()`
/// is what keeps that blend contained to this view instead of leaking into
/// unrelated content elsewhere on screen.
struct HighlightSweep: View {
var text: String = "worth remembering"
var highlight: Color = Color(red: 1.0, green: 0.878, blue: 0.4)
/// One of "block", "underline", "thin", "swash".
var style: String = "block"
var duration: Double = 520
/// Fraction of the line height the band covers.
var height: Double = 0.72
var radius: Double = 3
/// Preview override. When set, renders that point of the one-shot sweep.
var progress: Double? = nil
@Environment(\.accessibilityReduceMotion) private var reduceMotion
@State private var animated: Double = 0
private let fontSize: CGFloat = 30
var body: some View {
let a = reduceMotion ? 1 : (progress ?? animated)
return Text(text)
.font(.system(size: fontSize, weight: .semibold))
.background(alignment: .leading) {
GeometryReader { g in
band(size: g.size, a: a)
}
.accessibilityHidden(true)
}
.compositingGroup()
.onAppear(perform: start)
}
private func band(size: CGSize, a: Double) -> some View {
// Underline/thin ignore `height` and sit as a fixed-thickness bar on
// the baseline; block/swash use `height` as a fraction of the line,
// offset up from the baseline plus a small nudge so it clears
// descenders without reaching the cap line.
let (bandHeight, yOffset): (CGFloat, CGFloat) = {
switch style {
case "underline": return (6, size.height - 6)
case "thin": return (2, size.height - 2)
default: return (size.height * height, size.height * (1 - height) - fontSize * 0.1)
}
}()
// `swash` drifts a little vertically across the sweep — a raised
// sine peaking mid-flight — so the band reads as hand-drawn.
let drift: CGFloat = style == "swash" ? CGFloat(sin(a * .pi)) * 2 : 0
let rotation: Double = style == "swash" ? 1.5 : 0
return RoundedRectangle(cornerRadius: radius, style: .continuous)
.fill(highlight)
.frame(width: size.width * a, height: bandHeight)
.offset(y: yOffset + drift)
.rotationEffect(.degrees(rotation), anchor: .leading)
.blendMode(.multiply)
}
private func start() {
guard progress == nil, !reduceMotion else { return }
withAnimation(.easeOut(duration: duration / 1000)) {
animated = 1
}
}
}
#Preview {
HighlightSweep()
.frame(width: 380, height: 240)
.padding(24)
}
iOS 17 · No dependencies
SwiftUI note. `.compositingGroup()` is required or `.multiply` blends against whatever sits behind the whole page instead of staying local to this view.
Dependencies
- SwiftUI
- No external dependencies
- Flutter
- No external dependencies
Requires iOS 17
Requires Flutter 3.27


