Install any skill in seconds. Free to start, no credit card required.
Get Started Free →iOS 26 Liquid Glass design system — dynamic glass material with blur, reflection, and interactive morphing for SwiftUI, UIKit, and WidgetKit.
.claude/skills/loulanyue-liquid-glass-design/SKILL.md| Test case | Without → With | Effect | Δ tokens | Δ turns |
|---|---|---|---|---|
| case-01 | ✗→✓ | ▲ Improved | 6% | 0% |
| case-02 | ✗→✓ | ▲ Improved | 4% | 0% |
| case-03 | ✗→✓ | ▲ Improved | 71% | 0% |
| case-04 | ✗→✓ | ▲ Improved | 2% | 0% |
| case-05 | ✗→✓ | ▲ Improved | 28% | 0% |
Patterns for implementing Apple's Liquid Glass — a dynamic material that blurs content behind it, reflects color and light from surrounding content, and reacts to touch and pointer interactions. Covers SwiftUI, UIKit, and WidgetKit integration.
The simplest way to add Liquid Glass to any view:
swiftText("Hello, World!") .font(.title) .padding() .glassEffect() // Default: regular variant, capsule shape
swiftText("Hello, World!") .font(.title) .padding() .glassEffect(.regular.tint(.orange).interactive(), in: .rect(cornerRadius: 16.0))
Key customization options:
.regular — standard glass effect.tint(Color) — add color tint for prominence.interactive() — react to touch and pointer interactions.capsule (default), .rect(cornerRadius:), .circleswiftButton("Click Me") { /* action */ } .buttonStyle(.glass) Button("Important") { /* action */ } .buttonStyle(.glassProminent)
Always wrap multiple glass views in a container for performance and morphing:
swiftGlassEffectContainer(spacing: 40.0) { HStack(spacing: 40.0) { Image(systemName: "scribble.variable") .frame(width: 80.0, height: 80.0) .font(.system(size: 36)) .glassEffect() Image(systemName: "eraser.fill") .frame(width: 80.0, height: 80.0) .font(.system(size: 36)) .glassEffect() } }
The spacing parameter controls merge distance — closer elements blend their glass shapes together.
Combine multiple views into a single glass shape with glassEffectUnion:
swift@Namespace private var namespace GlassEffectContainer(spacing: 20.0) { HStack(spacing: 20.0) { ForEach(symbolSet.indices, id: \.self) { item in Image(systemName: symbolSet[item]) .frame(width: 80.0, height: 80.0) .glassEffect() .glassEffectUnion(id: item < 2 ? "group1" : "group2", namespace: namespace) } } }
Create smooth morphing when glass elements appear/disappear:
swift@State private var isExpanded = false @Namespace private var namespace GlassEffectContainer(spacing: 40.0) { HStack(spacing: 40.0) { Image(systemName: "scribble.variable") .frame(width: 80.0, height: 80.0) .glassEffect() .glassEffectID("pencil", in: namespace) if isExpanded { Image(systemName: "eraser.fill") .frame(width: 80.0, height: 80.0) .glassEffect() .glassEffectID("eraser", in: namespace) } } } Button("Toggle") { withAnimation { isExpanded.toggle() } } .buttonStyle(.glass)
To allow horizontal scroll content to extend under a sidebar or inspector, ensure the ScrollView content reaches the leading/trailing edges of the container. The system automatically handles the under-sidebar scrolling behavior when the layout extends to the edges — no additional modifier is needed.
swiftlet glassEffect = UIGlassEffect() glassEffect.tintColor = UIColor.systemBlue.withAlphaComponent(0.3) glassEffect.isInteractive = true let visualEffectView = UIVisualEffectView(effect: glassEffect) visualEffectView.translatesAutoresizingMaskIntoConstraints = false visualEffectView.layer.cornerRadius = 20 visualEffectView.clipsToBounds = true view.addSubview(visualEffectView) NSLayoutConstraint.activate([ visualEffectView.centerXAnchor.constraint(equalTo: view.centerXAnchor), visualEffectView.centerYAnchor.constraint(equalTo: view.centerYAnchor), visualEffectView.widthAnchor.constraint(equalToConstant: 200), visualEffectView.heightAnchor.constraint(equalToConstant: 120) ]) // Add content to contentView let label = UILabel() label.text = "Liquid Glass" label.translatesAutoresizingMaskIntoConstraints = false visualEffectView.contentView.addSubview(label) NSLayoutConstraint.activate([ label.centerXAnchor.constraint(equalTo: visualEffectView.contentView.centerXAnchor), label.centerYAnchor.constraint(equalTo: visualEffectView.contentView.centerYAnchor) ])
swiftlet containerEffect = UIGlassContainerEffect() containerEffect.spacing = 40.0 let containerView = UIVisualEffectView(effect: containerEffect) let firstGlass = UIVisualEffectView(effect: UIGlassEffect()) let secondGlass = UIVisualEffectView(effect: UIGlassEffect()) containerView.contentView.addSubview(firstGlass) containerView.contentView.addSubview(secondGlass)
swiftscrollView.topEdgeEffect.style = .automatic scrollView.bottomEdgeEffect.style = .hard scrollView.leftEdgeEffect.isHidden = true
swiftlet favoriteButton = UIBarButtonItem(image: UIImage(systemName: "heart"), style: .plain, target: self, action: #selector(favoriteAction)) favoriteButton.hidesSharedBackground = true // Opt out of shared glass background
swiftstruct MyWidgetView: View { @Environment(\.widgetRenderingMode) var renderingMode var body: some View { if renderingMode == .accented { // Tinted mode: white-tinted, themed glass background } else { // Full color mode: standard appearance } } }
swiftHStack { VStack(alignment: .leading) { Text("Title") .widgetAccentable() // Accent group Text("Subtitle") // Primary group (default) } Image(systemName: "star.fill") .widgetAccentable() // Accent group }
swiftImage("myImage") .widgetAccentedRenderingMode(.monochrome)
swiftVStack { /* content */ } .containerBackground(for: .widget) { Color.blue.opacity(0.2) }
| Decision | Rationale | |----------|-----------| | GlassEffectContainer wrapping | Performance optimization, enables morphing between glass elements | | spacing parameter | Controls merge distance — fine-tune how close elements must be to blend | | @Namespace + glassEffectID | Enables smooth morphing transitions on view hierarchy changes | | interactive() modifier | Explicit opt-in for touch/pointer reactions — not all glass should respond | | UIGlassContainerEffect in UIKit | Same container pattern as SwiftUI for consistency | | Accented rendering mode in widgets | System applies tinted glass when user selects tinted Home Screen |
.glassEffect() after other appearance modifiers (frame, font, padding).interactive() only on elements that respond to user interaction (buttons, toggleable items)withAnimation when changing view hierarchies to enable smooth morphing transitions.glassEffect() views without a GlassEffectContainerclipsToBounds = true in UIKit when using corner radii| Case | Status | Duration (ms) | Turns | Tokens | Tool calls | ||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| Without | With | Δ | Without | With | Δ | Without | With | Δ | Without | With | Δ | ||
case-01 | fail→pass | 17,438 | 8,268 | -53% | 1 | 1 | 0% | 3,693 | 3,924 | +6% | 0 | 0 | — |
case-02 | fail→pass | 23,042 | 12,075 | -48% | 1 | 1 | 0% | 4,609 | 4,801 | +4% | 0 | 0 | — |
case-03 | fail→pass | 11,062 | 6,087 | -45% | 1 | 1 | 0% | 1,857 | 3,177 | +71% | 0 | 0 | — |
case-04 | fail→pass | 14,245 | 3,630 | -75% | 1 | 1 | 0% | 2,802 | 2,861 | +2% | 0 | 0 | — |
case-05 | fail→pass | 12,540 | 3,793 | -70% | 1 | 1 | 0% | 2,274 | 2,922 | +28% | 0 | 0 | — |
case-06 | fail→pass | 13,212 | 4,367 | -67% | 1 | 1 | 0% | 2,349 | 3,093 | +32% | 0 | 0 | — |
case-07 | pass→pass | 11,422 | 4,114 | -64% | 1 | 1 | 0% | 1,821 | 2,879 | +58% | 0 | 0 | — |
case-08 | fail→pass | 24,021 | 6,930 | -71% | 1 | 1 | 0% | 4,198 | 3,622 | -14% | 0 | 0 | — |
case-09 | fail→pass | 12,278 | 3,104 | -75% | 1 | 1 | 0% | 1,931 | 2,780 | +44% | 0 | 0 | — |
case-10 | fail→pass | 13,906 | 2,120 | -85% | 1 | 1 | 0% | 2,083 | 2,592 | +24% | 0 | 0 | — |
case-11 | pass→pass | 10,505 | 4,479 | -57% | 1 | 1 | 0% | 1,946 | 3,015 | +55% | 0 | 0 | — |
case-20 | pass→pass | 8,447 | 5,780 | -32% | 1 | 1 | 0% | 1,546 | 3,277 | +112% | 0 | 0 | — |
case-12 | pass→pass | 6,021 | 3,124 | -48% | 1 | 1 | 0% | 980 | 2,730 | +179% | 0 | 0 | — |
case-13 | fail→pass | 9,680 | 2,903 | -70% | 1 | 1 | 0% | 1,582 | 2,674 | +69% | 0 | 0 | — |
case-14 | pass→pass | 5,941 | 3,198 | -46% | 1 | 1 | 0% | 995 | 2,708 | +172% | 0 | 0 | — |
case-15 | pass→pass | 10,280 | 3,443 | -67% | 1 | 1 | 0% | 1,432 | 2,798 | +95% | 0 | 0 | — |
case-21 | pass→fail | 16,222 | 12,586 | -22% | 1 | 1 | 0% | 3,110 | 4,635 | +49% | 0 | 0 | — |
case-16 | fail→pass | 17,185 | 6,819 | -60% | 1 | 1 | 0% | 2,737 | 3,495 | +28% | 0 | 0 | — |
case-17 | pass→pass | 5,587 | 2,499 | -55% | 1 | 1 | 0% | 979 | 2,692 | +175% | 0 | 0 | — |
case-18 | fail→pass | 11,951 | 2,943 | -75% | 1 | 1 | 0% | 1,793 | 2,767 | +54% | 0 | 0 | — |
case-19 | pass→pass | 10,578 | 3,578 | -66% | 1 | 1 | 0% | 1,566 | 2,885 | +84% | 0 | 0 | — |
case-22 | pass→pass | 10,350 | 10,992 | +6% | 1 | 1 | 0% | 1,996 | 4,271 | +114% | 0 | 0 | — |
DecimalAI ran this skill against gemini-3.6-flash twice over the same eval suite — once with the skill loaded and once without — and compared the two runs case by case. 22 cases were attempted. The headline lift of +50 percentage points is the difference between those two pass rates over the 22 comparable cases. 1 case got worse with the skill loaded, and it is included in that figure.
Without the skill loaded, the model failed this case. With it loaded, the same prompt on the same model passed. This is one improved case from the latest verified run; every case, including any that regressed, is in the table above.
Other measured skills in the registry, with their headline benchmark lift.