DEV Community

Cover image for Exploring Liquid Glass: Apple's Bold Redesign & System UI Changes in iOS 26 with SwiftUI Code Samples
Viennarz Curtiz
Viennarz Curtiz

Posted on

Exploring Liquid Glass: Apple's Bold Redesign & System UI Changes in iOS 26 with SwiftUI Code Samples

This is the first article in a multi-part series diving into Apple’s new Liquid Glass design language introduced in iOS 26. In this post, we’ll cover the basics and how to apply .glassEffect() across common SwiftUI views. Future articles will explore advanced custom containers, UIKit implementation, and new tab/navigation structures.

this post is beginner-friendly but expects iOS26 + Xcode 26 beta

🔍 Upcoming in this series:

  • Part 2: Deep Dive on Tab Bars, Navigation, and Sidebars
  • Part 3: Creating Custom Containers with .glassEffect()
  • Part 4: Implementing Liquid Glass in UIKit

Whether you’re designing a modern SwiftUI app or just experimenting with iOS 26, this guide will give you a strong foundation for working with the new visual paradigm.


Table Of Contents


1. Brief introduction to Liquid Glass design—what it is and why it matters

I am assuming that you are aware of the announcement from WWDC25, where Apple made their broadest redesign, and presented "Liquid Glass".


Apple Developer documentation overview states:

Interfaces across Apple platforms feature a new dynamic material called Liquid Glass, which combines the optical properties of glass with a sense of fluidity.



Here are some videos for some refresher. I suggest you watch these before we dive in. 🤿


2. Tools and Setup


What you need.

  • Xcode 26 (as of writing, we are using the beta)
  • Make sure the project's minimum target is iOS26

Random Example - where I compiled my app in Xcode26. see the Tab bar.
Tab bar on Liquid Glass


3. SwiftUI Examples with .glassEffect()


Applying Glass Effect on Containers VStack and HStack

VStack with glass effect

VStack {
      Text("Large Title")
          .font(.largeTitle)

      Text("Subheadline")
          .font(.subheadline)

      Text("body")
          .font(.body)
}
.glassEffect()
Enter fullscreen mode Exit fullscreen mode

HStack with glass effect

 HStack {
    Text("Large Title")
        .font(.largeTitle)

    Text("Subheadline")
        .font(.subheadline)

    Text("body")
        .font(.body)
 }
 .glassEffect()

Enter fullscreen mode Exit fullscreen mode

VStack and HStack in a ScrollView


Applying Glass Effect on GroupBox

note that the padding inside the content is automatically applied

    GroupBox {
       HStack {
         Text("Large Title")
             .font(.largeTitle)

         Text("Subheadline")
             .font(.subheadline)

         Text("body")
             .font(.body)
        }
     }
     .glassEffect()

Enter fullscreen mode Exit fullscreen mode

Groubox
It looks like it has No effect, but if we apply .backgroundStyle(Color.red) , we can see that they have overlapped with each other.

 .glassEffect()
 .backgroundStyle(Color.red)

Enter fullscreen mode Exit fullscreen mode

Group Box

so it might be considered a workaround if we set .backgroundStyle(Color.clear) to the GroupBox

GroupBox


Applying Glass Effect on Forms

No effect when applying to Form container

 Form {
    Section("section 1") {
         TextField("Enter text", text: $text)
         TextField("Enter text", text: $text2)

    }

    Section("section 2") {

         TextField("Enter text", text: $text)
            .padding()

         TextField("Enter text", text: $text2)
             .padding()

     }

  }
  .glassEffect()

Enter fullscreen mode Exit fullscreen mode

Forms

 Form {
    Section("section 1") {
        TextField("Enter text", text: $text)
        TextField("Enter text", text: $text2)

    }


    Section("section 2") {

        TextField("Enter text", text: $text)
            .padding()

        TextField("Enter text", text: $text2)
            .padding()

    }

}
.glassEffect()

Enter fullscreen mode Exit fullscreen mode

only applies to the inner child views when attached in Section

Forms

 Section("section 2") {

    TextField("Enter text", text: $text)
        .padding()

    TextField("Enter text", text: $text2)
        .padding()

}
.glassEffect()

Enter fullscreen mode Exit fullscreen mode

Button with buttonStyle Glass

Button

Button

Button

Button {
    //action    
} label: {
    Text("Glass Button")
}
.buttonStyle(.glass)

Enter fullscreen mode Exit fullscreen mode

Tinted

Button - tinted

Button {
//Action

} label: {
    Text("Glass Button")
}
.buttonStyle(.glass)
.tint(.red)

Enter fullscreen mode Exit fullscreen mode

Different Button Shapes

Capsule

Circle

Rounded Rectangle


Toggles

Toggle

Toggle toggleStyle(.button)


Toggle(isOn: $toggleValue1) {
    Text("Toggle - Button Style")
}
.toggleStyle(.button)
.glassEffect()

Enter fullscreen mode Exit fullscreen mode

Toggle in GroupBox Container

Toggle in groupbox

GroupBox {
    Toggle(isOn: $toggleValue1) {
        Text("Toggle")
    }
    .labelsHidden()
    .tint(.orange)
}
Enter fullscreen mode Exit fullscreen mode

Toggle with Gradient Background Style

Toggle

Toggle(isOn: $toggleValue1) {
    Text("Toggle")
}
.labelsHidden()
.padding(32)
.background {
    RoundedRectangle(cornerRadius: 25)
        .fill(
            RadialGradient(
                colors: [Color.green, Color.blue, Color.teal],
                center: .bottomLeading,
                startRadius: 50,
                endRadius: 300
            )
        )
}
.tint(Color.red)

Enter fullscreen mode Exit fullscreen mode

Toggle with Material Backgrounds

Toggle with material bg

Toggle(isOn: $toggleValue1) {
    Text("Toggle")
}
.labelsHidden()
.padding(32)
.background {
    RoundedRectangle(cornerRadius: 25)
        .fill(
            Material.ultraThin
        )
}

Enter fullscreen mode Exit fullscreen mode

Slider

Slider 1

Slider 2

Slider - with labels

Silder 3

Slider(
    value: $sliderValue,
    in: 0...100,
    label: {
        Label("Label", systemImage: "smartphone")
    },
    minimumValueLabel: {
        Text("Minimum Label")
    },
    maximumValueLabel: {
        Text("Maximum Label")
    }
)

Enter fullscreen mode Exit fullscreen mode

Sliders embedded in a GroupBox

Slider

GroupBox {
    Slider(value: $sliderValue, in: 0...100)
}
.backgroundStyle(Material.ultraThin)


GroupBox {
    Slider(value: $sliderValue, in: 0...100)
        .padding()
}
.backgroundStyle(
    LinearGradient(
        colors: [Color.red, Color.orange, Color.teal],
        startPoint: .leading,
        endPoint: .trailing
    )
)
.tint(.yellow)
Enter fullscreen mode Exit fullscreen mode

Slider - with stepper

Slider

Slider(value: $sliderValue, in: 0...100, step: 10)
Enter fullscreen mode Exit fullscreen mode

Picker

with .glassEffect Modifier

Picker

Picker(selection: $selectedRegion) {
    Text("NCR").tag(String("NCR"))
    Text("Ilocos Region").tag(String("Ilocos Region"))
    Text("Central Luzon").tag(String("Central Luzon"))
    Text("SOCCSKSARGEN").tag(String("SOCCSKSARGEN"))
    Text("BARMM").tag(String("BARMM"))
} label: {
    Text("Select")
}
.glassEffect()
.tint(.white)
Enter fullscreen mode Exit fullscreen mode

without the .glassEffect Modifier, the picker button does not have the glass effect, or embedded in a glass container

Picker 2

Picker(selection: $selectedRegion) {
    Text("NCR").tag(String("NCR"))
    Text("Ilocos Region").tag(String("Ilocos Region"))
    Text("Central Luzon").tag(String("Central Luzon"))
    Text("SOCCSKSARGEN").tag(String("SOCCSKSARGEN"))
    Text("BARMM").tag(String("BARMM"))
} label: {
    Text("Select")
}
.tint(.white)
Enter fullscreen mode Exit fullscreen mode

Segmented Picker style

with .glassEffect() modifier

Segmented Picker

Picker(selection: $selectedRegion) {
  Text("NCR").tag(String("NCR"))
  Text("Ilocos Region").tag(String("Ilocos Region"))
  Text("Central Luzon").tag(String("Central Luzon"))
  Text("SOCCSKSARGEN").tag(String("SOCCSKSARGEN"))
  Text("BARMM").tag(String("BARMM"))
} label: {
  Text("Select")
}
.glassEffect()
.pickerStyle(.segmented)
Enter fullscreen mode Exit fullscreen mode

without the .glassEffect() modifier

Picker 3

Picker 4

   Picker(selection: $selectedRegion) {
      Text("NCR").tag(String("NCR"))
      Text("Ilocos Region").tag(String("Ilocos Region"))
      Text("Central Luzon").tag(String("Central Luzon"))
      Text("SOCCSKSARGEN").tag(String("SOCCSKSARGEN"))
      Text("BARMM").tag(String("BARMM"))
  } label: {
      Text("Select")
  }
  .pickerStyle(.segmented)
Enter fullscreen mode Exit fullscreen mode

Menu Picker Style

With image background

Menu Picker 1

Without image background

Menu Picker 2

Menu Picker 3

Menu Picker 4

 Picker(selection: $selectedRegion) {
      Text("NCR").tag(String("NCR"))
      Text("Ilocos Region").tag(String("Ilocos Region"))
      Text("Central Luzon").tag(String("Central Luzon"))
      Text("SOCCSKSARGEN").tag(String("SOCCSKSARGEN"))
      Text("BARMM").tag(String("BARMM"))

      Picker("Sub Picker", selection: $selectedRegion) {
          Text("Item 1").tag(String("Item 1"))
          Text("Item 2").tag(String("Item 2"))

          Menu {
              Text("Menu").tag(String("Menu"))

          } label: {
              Text("Menu Item")
          }

      }

    } label: {
      Text("Select")
    }
    .pickerStyle(.menu)
Enter fullscreen mode Exit fullscreen mode

Picker containing a child Picker with Palette style

Picker

Picker

Picker


Menu

Menu 1

Menu 2

Menu {
    ForEach(Option.allCases, id: \.self) { option in
        Button {
            selectedOption = option

        } label: {
            Label(
                option.displayTitle,
                systemImage: option.imageSystemName
            )
        }


    }

    Menu {
        ForEach(Vehicle.allCases, id: \.self) { option in
            Button {
                selectedSub = option

            } label: {
                Label(
                    option.displayTitle,
                    systemImage: option.imageSystemName
                )
            }

        }
    } label: {
        Label("Submenu", systemImage: "car")
    }


} label: {
    Text("Menu")
}
.foregroundStyle(.primary, .red)
.padding()
.glassEffect()
Enter fullscreen mode Exit fullscreen mode

Date Picker

Apply glassEffect modifier

Date Picker


 VStack {
    GroupBox {
        Text("With Glass effect")
    }

    DatePicker(
        "Date Picker",
        selection: $selectedDate,
        displayedComponents: [.hourAndMinute, .date]
    )
    .padding(.horizontal)
    .glassEffect()
    .padding(.horizontal)
}
Enter fullscreen mode Exit fullscreen mode

Apply glass effect on the graphical style? hmm

Calendar glass

VStack {
    GroupBox {
        Text("Graphical date picker style + Glass Effect")
    }
    GlassEffectContainer {
        DatePicker(
            "Date Picker",
            selection: $selectedDate,
            displayedComponents: [.hourAndMinute, .date]
        )
        .padding()
        .datePickerStyle(.graphical)
        .glassEffect()
    }
    .padding()
}
Enter fullscreen mode Exit fullscreen mode

Color Picker

We can apply .glassEffect() modifier to the Color picker container.

The Liquid Glass style on Sliders are automatically applied on iOS26.


Gauge

Apply glassEffect modifier to Gauge with different gauge styles


VStack(spacing: 64) {

    Gauge(value: 0.5, label: { Text("50%") })
        .padding()
        .glassEffect()

    Gauge(value: 0.2, label: { Text("20%") })
        .padding()
        .glassEffect()
        .gaugeStyle(.accessoryCircular)

    Gauge(value: 0.8, label: { Text("80%") })
        .padding()
        .glassEffect()
        .gaugeStyle(.accessoryCircularCapacity)

    Gauge(value: 0.7, label: { Text("70%") })
        .padding()
        .glassEffect()
        .gaugeStyle(.accessoryLinear)

    Gauge(value: 0.8, label: { Text("80%") })
        .padding()
        .glassEffect()
        .gaugeStyle(.accessoryLinearCapacity)

}
.padding(.horizontal)
Enter fullscreen mode Exit fullscreen mode

Progress View


ProgressView("Progress", value: 50, total: 100)
    .padding()
    .glassEffect()

ProgressView("Progress", value: 50, total: 100)
    .padding()
    .glassEffect()
    .progressViewStyle(.circular)

ProgressView(value: 50, total: 100)
    .padding()
    .glassEffect()
    .progressViewStyle(.circular)
Enter fullscreen mode Exit fullscreen mode

Stepper

Adding glassEffect to Stepper


Stepper(value: $stepperValue.animation(.bouncy), in: 0...100) {
    Text("\(stepperValue)")
        .font(.title.bold())
        .contentTransition(.numericText())
}
.padding()
.glassEffect()
Enter fullscreen mode Exit fullscreen mode

TextField

Apply .glassEffect() on TextField

TextField 1

TextField("Textfield", text: $textFieldValue)
    .padding()
    .glassEffect()
Enter fullscreen mode Exit fullscreen mode

.glassEffect(), TextFieldStyle - roundedBorder

TextField 2

  TextField("Textfield", text: $textFieldValue)
      .padding()
      .glassEffect()
      .textFieldStyle(.roundedBorder)
Enter fullscreen mode Exit fullscreen mode

.glassEffect(), TextFieldStyle - roundedBorder, and axis is vertical


`.glassEffect()`, TextFieldStyle - roundedBorder, and axis is vertical
Enter fullscreen mode Exit fullscreen mode

Apply glassEffect on TextField, with axis set on vertical

Textfield

TextField("Textfield", text: $textFieldValue, axis: .vertical)
    .padding()
    .glassEffect()
Enter fullscreen mode Exit fullscreen mode

Presentations and Overlays

Popovers on small screen

Note that popovers appears as modals when on compact screen, on regular screens like iPad, it will look more a popover

Popover

Popovers on larger screen

Popover

.popover(isPresented: $popoverIsPresented, content: {
      Text("Popover Content")
          .padding()
          .presentationDetents([.height(100), .medium, .large])
  })

Enter fullscreen mode Exit fullscreen mode

Sheet

Sheet

Sheet

.sheet(isPresented: $presentSheet) {
    Text("Sheet Content")
        .presentationDetents([.height(100), .medium, .large])
}
Enter fullscreen mode Exit fullscreen mode

Action Sheet / Confirmation Dialog


.confirmationDialog(
  "Confirmation",
  isPresented: $showActionSheet) {
      Button("OK") {

      }

      Button("Destructive", role: .destructive) {

      }

      Button("Close", role: .close) {

      }

      Button("Confirm", role: .confirm) {

      }
  }
Enter fullscreen mode Exit fullscreen mode

Alert


.alert("Alert", isPresented: $showAlert) {
    Button("OK", role: .cancel) {

    }

    Button("Destructive", role: .destructive) {

    }

    Button("Close", role: .close) {

    }

    Button("Confirm", role: .confirm) {

    }

    Button("Button") {

    }


} message: {
    Text("Some Message Texts")
}
Enter fullscreen mode Exit fullscreen mode

Tab bar

Tab

Tab bar

Tab with Role - search


4. Observations, Quirks, and Gotchas

Native Navigation Back button - don’t have title of the previous screen

Back button


Retained behavior: when long pressing the back button shows list of previous screens

Navigation

Customizing the Navigation back button

using toolbar does not work, and cannot override even using .labelsVisibility(.visible) , the button

struct DestinationNormalView: View {
    var body: some View {
        VStack {
            Text("DestinationNormalView")
                .navigationBarBackButtonHidden()
                .toolbar {
                    ToolbarItem(
                        id: "back-button",
                        placement: .topBarLeading) {
                            Button {
                            } label: {
                                Label("Back", systemImage: "chevron.left")
                                    .labelsVisibility(.visible)
                            }
                        }
                }

        }
    }
}


Enter fullscreen mode Exit fullscreen mode
Alternatively, you can customize your own back, though you will lose the ability to do the long-press gesture showing the previous screens.

Approach 1.

Approach 1

var body: some View {
        ZStack {
            VStack {
                Text("DestinationNormalView")
            }
            VStack {
                HStack(spacing: 4) {
                    Button {
                        presentationMode.wrappedValue.dismiss()
                    } label: {
                        Label("Back", systemImage: "chevron.left")
                    }

                    Spacer()
                }
                .padding(.horizontal)

                Spacer()
            }
            .navigationBarBackButtonHidden()

        }
    }


Enter fullscreen mode Exit fullscreen mode

Approach 2.


    var body: some View {
        ZStack {
            Color.white
            VStack {
                Text("DestinationUsingOverlayView")
            }

        }
        .ignoresSafeArea(.all)
        .background(Color.blue)
        .overlay(alignment: .top) {
            HStack(spacing: 4) {
                Button {
                    presentationMode.wrappedValue.dismiss()
                } label: {
                    Label("Back", systemImage: "chevron.left")
                }

                Spacer()
            }
            .padding(.horizontal)

        }
        .navigationBarBackButtonHidden()
    }

Enter fullscreen mode Exit fullscreen mode

📘 What’s Next?

This is just the beginning of our journey into Liquid Glass and the new UI possibilities in iOS 26.

In the next article, we’ll take a closer look at how system components like Tab Bars, Navigation, and Sidebars behave under Liquid Glass—and how you can customize or override them using SwiftUI.

If you found this helpful, consider following me here on Dev.to — and feel free to comment below with your thoughts, issues, or requests for what you’d like to see next.

Thanks for reading, and happy coding! 💧💎

Resources

Adopting Liquid Glass
Sample Code: Landmarks: Building an app with Liquid Glass

Top comments (0)