agentsclimarketplace

Liquid glass containers

Skill SohrabZ/liquid-glass-skills/liquid-glass-containers

Combine, group, and morph multiple Liquid Glass elements in iOS 26 SwiftUI. Covers GlassEffectContainer (required whenever you have more than one glass element — glass cannot sample other glass), morphing transitions with glassEffectID + a shared @Namespace, glassEffectUnion for merging distant elements, and glassEffectTransition (.identity / .matchedGeometry / .materialize). Use this skill when the user has two or more glass views, wants glass buttons/icons that "blend", "merge", or "morph" into each other, is building an expandable/collapsing cluster of glass controls, asks "why does my glass look wrong with multiple elements", "how do I animate between glass states", mentions "GlassEffectContainer", "glassEffectID", "glassEffectUnion", "matchedGeometry" for glass, or needs fluid transitions between glass shapes.From its SKILL.md

Install
npx -y skills add SohrabZ/liquid-glass-skills --skill liquid-glass-containers

Assembled from the repository path, not quoted from the project. Check it against their README if it does not work.

2 things to look at

  • no licenseNo license file was found in the repository. Code published without one is not open source by default, so using it at work is a question for whoever answers licensing questions where you are.
  • 1 stars1 stars. Stars are a popularity signal and not a quality one, but at this level it is likely that nobody has read this closely except its author, and you would be relying on your own review.

SKILL.md

6.9 KB, ~1.3k tokens by cl100k_base, as published. Nobody here has run it

Liquid Glass Containers, Morphing & Unions

The core constraint: glass cannot sample other glass. Any time you have more than one glass element near each other, wrap them in a GlassEffectContainer. It provides a shared sampling region, improves rendering performance, and is what enables morphing between elements. (Prereqs: see liquid-glass-foundations for .glassEffect() basics.)

GlassEffectContainer

GlassEffectContainer {
    HStack(spacing: 20) {
        Image(systemName: "pencil")
            .frame(width: 44, height: 44)
            .glassEffect(.regular.interactive())
        Image(systemName: "eraser")
            .frame(width: 44, height: 44)
            .glassEffect(.regular.interactive())
    }
}

The spacing: parameter is the morphing threshold — glass elements within this distance visually blend and morph together during transitions:

GlassEffectContainer(spacing: 40.0) {
    ForEach(icons) { icon in
        IconView(icon).glassEffect()
    }
}

struct GlassEffectContainer<Content: View>: View {
    init(spacing: CGFloat? = nil, @ViewBuilder content: () -> Content)
    init(@ViewBuilder content: () -> Content)
}

Morphing transitions with glassEffectID

Requirements for morphing:

  1. Elements live in the same GlassEffectContainer.
  2. Each view has a glassEffectID sharing one @Namespace.
  3. Views are conditionally shown/hidden to trigger the morph.
  4. The state change is wrapped in withAnimation.
struct MorphingExample: View {
    @State private var isExpanded = false
    @Namespace private var namespace

    var body: some View {
        GlassEffectContainer(spacing: 30) {
            Button(isExpanded ? "Collapse" : "Expand") {
                withAnimation(.bouncy) { isExpanded.toggle() }
            }
            .glassEffect()
            .glassEffectID("toggle", in: namespace)

            if isExpanded {
                Button("Action 1") { }
                    .glassEffect()
                    .glassEffectID("action1", in: namespace)
                Button("Action 2") { }
                    .glassEffect()
                    .glassEffectID("action2", in: namespace)
            }
        }
    }
}

func glassEffectID<ID: Hashable>(_ id: ID, in namespace: Namespace.ID) -> some View

Expandable action menu — a toggle that's always visible, surrounded by buttons that morph in and out. Each conditional button gets its own glassEffectID; the toggle keeps a stable id:

struct ActionButtonsView: View {
    @State private var showActions = false
    @Namespace private var namespace

    var body: some View {
        ZStack {
            Image("background").resizable().ignoresSafeArea()

            GlassEffectContainer(spacing: 30) {
                VStack(spacing: 30) {
                    if showActions {
                        actionButton("rotate.right").glassEffectID("rotate", in: namespace)
                    }
                    HStack(spacing: 30) {
                        if showActions {
                            actionButton("circle.lefthalf.filled").glassEffectID("contrast", in: namespace)
                        }
                        actionButton(showActions ? "xmark" : "slider.horizontal.3") {
                            withAnimation(.bouncy) { showActions.toggle() }
                        }
                        .glassEffectID("toggle", in: namespace)
                        if showActions {
                            actionButton("flip.horizontal").glassEffectID("flip", in: namespace)
                        }
                    }
                    if showActions {
                        actionButton("crop").glassEffectID("crop", in: namespace)
                    }
                }
            }
        }
    }

    @ViewBuilder
    func actionButton(_ systemImage: String, action: (() -> Void)? = nil) -> some View {
        Button { action?() } label: {
            Image(systemName: systemImage).frame(width: 44, height: 44)
        }
        .buttonStyle(.glass)
        .buttonBorderShape(.circle)
    }
}

glassEffectUnion — merge distant elements

When elements are too far apart to merge via spacing alone, force them into one visual glass shape. All conditions must hold: same id, same glass effect type, similar shapes.

func glassEffectUnion<ID: Hashable>(id: ID, namespace: Namespace.ID) -> some View

struct UnionExample: View {
    @Namespace var controls
    var body: some View {
        GlassEffectContainer {
            VStack(spacing: 0) {
                Button("Edit") { }
                    .buttonStyle(.glass)
                    .glassEffectUnion(id: "tools", namespace: controls)
                Spacer().frame(height: 100)     // large gap, still unified
                Button("Delete") { }
                    .buttonStyle(.glass)
                    .glassEffectUnion(id: "tools", namespace: controls)
            }
        }
    }
}

Use different ids to form subgroups — here the first 3 icons blend, the 4th floats separately:

GlassEffectContainer {
    ForEach(0..<4) { index in
        Image(systemName: icons[index])
            .frame(width: 70, height: 70)
            .glassEffect()
            .glassEffectUnion(id: index < 3 ? "group1" : "group2", namespace: glassNamespace)
    }
}

glassEffectTransition

Controls how a glass element appears/disappears during a morph.

func glassEffectTransition(_ transition: GlassEffectTransition, isEnabled: Bool = true) -> some View

enum GlassEffectTransition {
    case identity        // no changes
    case matchedGeometry // matched-geometry transition (default)
    case materialize     // material appearance transition
}

Button("Action") { }
    .glassEffect()
    .glassEffectID("button", in: namespace)
    .glassEffectTransition(.materialize)

Related skills

  • Applying glass to standard components (buttons/toolbars/tabs) → liquid-glass-controls
  • Larger compositions like floating action clusters → liquid-glass-advanced
  • Performance rationale for always using a container → liquid-glass-best-practices

What ships with it

Read from the repository

Just SKILL.md. No reference files, no scripts.

Keep looking

Skills are one crate of 325,949. Ordering is by how many stacks a row turns up in, so the top of any crate is what has actually been picked rather than what has the most stars.