
Conic Rim
From syxUI — written for both platforms, not translated between them.
A card ringed by a rotating conic gradient, one bright comet head circling the border.
Cards · SwiftUI · Flutter · iOS 17 · Flutter 3.27
- card
- border
- conic
- gradient
- glow
- animated
- loop
The actual source
DepthConicRim.swift
// Conic Rim · syxUI · https://syxui.dev/components/depth-conic-rim
// Free in any project. Keep this line and credit syxUI where a person can read it.
import SwiftUI
/// A card ringed by a rotating conic gradient — one bright comet head circling
/// the border with a long tail fading out behind it.
///
/// The entire effect is a single `Double`: the angle the `AngularGradient`
/// starts at. No geometry moves and nothing re-lays out, so it costs one
/// gradient rasterisation per frame regardless of card size. Built for dark
/// surfaces — over white the rim has nothing to glow against.
struct DepthConicRim: View {
var accentA: Color = Color(red: 0.431, green: 0.906, blue: 0.976)
var accentB: Color = Color(red: 0.659, green: 0.333, blue: 0.969)
var surface: Color = Color(red: 0.043, green: 0.051, blue: 0.078)
var borderWidth: CGFloat = 1.5
var glowRadius: CGFloat = 12
/// Seconds for one full turn of the rim.
var period: Double = 4
/// Preview override. When set, the component renders that point of one
/// loop instead of driving its own clock.
var phase: Double? = nil
@Environment(\.accessibilityReduceMotion) private var reduceMotion
@State private var animated: Double = 0
private var current: Double { phase ?? animated }
private let corner: CGFloat = 24
private var shape: RoundedRectangle {
RoundedRectangle(cornerRadius: corner, style: .continuous)
}
/// A short bright arc leading a long fade to nothing.
///
/// Weighting the stops rather than spacing them evenly is the whole trick:
/// evenly spaced stops read as a spinning rainbow, this reads as one
/// travelling head. The last two stops are both `.clear` so three quarters
/// of the ring stays dark and the head has somewhere to arrive from.
private var stops: [Gradient.Stop] {
[
.init(color: accentA, location: 0),
.init(color: accentB, location: 0.08),
.init(color: accentB.opacity(0.15), location: 0.28),
.init(color: .clear, location: 0.55),
.init(color: .clear, location: 1),
]
}
var body: some View {
// Linear, never eased: any easing on a full-turn loop reads as a
// stutter every time the cycle wraps.
let rim = AngularGradient(
stops: stops,
center: .center,
angle: .degrees(360 * current)
)
content
.padding(22)
.background {
shape.fill(surface)
// Interior lift: a 6% wash toward the top edge so the card is
// not a flat black rectangle sitting inside a bright ring.
shape.fill(
LinearGradient(
colors: [.white.opacity(0.06), .clear],
startPoint: .top,
endPoint: .center
)
)
}
.overlay {
shape.strokeBorder(rim, lineWidth: borderWidth)
}
.background {
// Bloom: the same stroke, fatter and blurred, sitting behind
// the opaque surface so only the outer half shows.
shape
.strokeBorder(rim, lineWidth: borderWidth * 2.2)
.blur(radius: glowRadius)
.opacity(0.55)
}
.onAppear(perform: start)
.accessibilityElement(children: .combine)
}
private var content: some View {
VStack(alignment: .leading, spacing: 18) {
HStack(spacing: 13) {
RoundedRectangle(cornerRadius: 13, style: .continuous)
.fill(
LinearGradient(
colors: [accentA, accentB],
startPoint: .topLeading,
endPoint: .bottomTrailing
)
)
.frame(width: 44, height: 44)
.overlay {
Image(systemName: "waveform")
.font(.system(size: 19, weight: .semibold))
.foregroundStyle(Color(white: 0.05))
}
VStack(alignment: .leading, spacing: 3) {
Text("Neural sync")
.font(.system(size: 17, weight: .semibold))
.foregroundStyle(.white)
Text("4 channels · 96 kHz")
.font(.system(size: 12.5))
.foregroundStyle(.white.opacity(0.45))
}
Spacer(minLength: 8)
Text("LIVE")
.font(.system(size: 10, weight: .bold))
.tracking(0.8)
.foregroundStyle(accentA)
.padding(.horizontal, 8)
.padding(.vertical, 4)
.background { Capsule().fill(accentA.opacity(0.14)) }
}
HStack(spacing: 0) {
stat("18.4", "ms latency")
stat("99.2", "% uptime")
stat("2.1k", "packets")
}
}
.frame(width: 268, alignment: .leading)
}
private func stat(_ value: String, _ label: String) -> some View {
VStack(alignment: .leading, spacing: 2) {
Text(value)
.font(.system(size: 15, weight: .semibold, design: .rounded))
.foregroundStyle(.white.opacity(0.92))
Text(label)
.font(.system(size: 10.5))
.foregroundStyle(.white.opacity(0.38))
}
.frame(maxWidth: .infinity, alignment: .leading)
}
private func start() {
guard phase == nil, !reduceMotion, period > 0 else { return }
withAnimation(.linear(duration: period).repeatForever(autoreverses: false)) {
animated = 1
}
}
}
#Preview {
DepthConicRim()
.frame(width: 380, height: 240)
.background(Color(white: 0.02))
}
iOS 17 · No dependencies
SwiftUI note. The rotation must stay .linear — easing a full-turn loop reads as a stutter every time it wraps. The bloom is a second strokeBorder behind an opaque surface, so drop glowRadius to 0 before you reach for anything else if the card is on a scroll hot path.
Dependencies
- SwiftUI
- No external dependencies
- Flutter
- No external dependencies
Requires iOS 17
Requires Flutter 3.27


