# Glass Surface

A liquid-glass panel that bends and splits the light behind its edges. In Chromium it uses an SVG displacement filter on `backdrop-filter`, so it bends anything behind it. In Safari and Firefox, pass the image, video or canvas behind it as `backdrop` and it refracts it with WebGL instead; without one it falls back to frosted glass. Give it a `shape` (an SVG or transparent PNG) and the glass takes that outline. In the demo, move the cursor to make the glass drift, drag it across the sky, and switch presets in the panel.

<demo-glass-surface>



</demo-glass-surface>

## Installation

```bash
npx shadcn-vue@latest add "https://nxui.geoql.in/r/glass-surface.json"
```

## Usage

```vue
<script setup lang="ts">
  import { ref } from 'vue';
  import GlassSurface from '~/components/ui/glass-surface/GlassSurface.vue';

  const sky = ref<HTMLImageElement | null>(null);
</script>

<template>
  <div class="relative flex h-125 items-center justify-center overflow-hidden">
    <img
      ref="sky"
      src="/sky.webp"
      alt=""
      crossorigin="anonymous"
      class="absolute inset-0 size-full object-cover"
    />
    <GlassSurface
      :width="320"
      :height="76"
      :border-radius="38"
      :displace="0.5"
      :background-opacity="0.1"
      :backdrop="sky"
    >
      <Icon name="lucide:house" class="size-6" />
    </GlassSurface>
  </div>
</template>
```

For a logo-shaped glass, pass `shape="/logo.svg"`. The image is fitted inside `width` × `height`, and its alpha channel becomes the outline and the bending edge.

## Props

<table>
<thead>
  <tr>
    <th>
      Prop
    </th>
    
    <th>
      Type
    </th>
    
    <th>
      Default
    </th>
    
    <th>
      Description
    </th>
  </tr>
</thead>

<tbody>
  <tr>
    <td>
      <code>
        width
      </code>
    </td>
    
    <td>
      <code>
        number | string
      </code>
    </td>
    
    <td>
      <code>
        200
      </code>
    </td>
    
    <td>
      Width in px, or any CSS value like <code>
        '100%'
      </code>
      
      .
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        height
      </code>
    </td>
    
    <td>
      <code>
        number | string
      </code>
    </td>
    
    <td>
      <code>
        80
      </code>
    </td>
    
    <td>
      Height in px, or any CSS value like <code>
        '100vh'
      </code>
      
      .
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        border-radius
      </code>
    </td>
    
    <td>
      <code>
        number
      </code>
    </td>
    
    <td>
      <code>
        20
      </code>
    </td>
    
    <td>
      Corner radius in px. Not used with a shape.
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        shape
      </code>
    </td>
    
    <td>
      <code>
        string
      </code>
    </td>
    
    <td>
      —
    </td>
    
    <td>
      URL of an SVG or transparent PNG that gives the glass its outline. Files from another origin need CORS headers.
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        refraction
      </code>
    </td>
    
    <td>
      <code>
        boolean
      </code>
    </td>
    
    <td>
      <code>
        true
      </code>
    </td>
    
    <td>
      Bends what is behind the glass at its edges. <code>
        false
      </code>
      
       uses the frosted look everywhere.
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        renderer
      </code>
    </td>
    
    <td>
      <code>
        'auto' | 'svg' | 'webgl'
      </code>
    </td>
    
    <td>
      <code>
        'auto'
      </code>
    </td>
    
    <td>
      <code>
        auto
      </code>
      
       uses SVG filters in Chromium and WebGL in Safari/Firefox when a <code>
        backdrop
      </code>
      
       is given. <code>
        webgl
      </code>
      
       forces WebGL whenever a backdrop is given.
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        backdrop
      </code>
    </td>
    
    <td>
      <code>
        HTMLImageElement | HTMLVideoElement | HTMLCanvasElement
      </code>
    </td>
    
    <td>
      —
    </td>
    
    <td>
      The media behind the glass. WebGL can only bend what it is given. Cross-origin images need CORS headers.
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        distortion-scale
      </code>
    </td>
    
    <td>
      <code>
        number
      </code>
    </td>
    
    <td>
      <code>
        -180
      </code>
    </td>
    
    <td>
      How strongly the edge bends what is behind it. Negative values pull the view inward.
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        border-width
      </code>
    </td>
    
    <td>
      <code>
        number
      </code>
    </td>
    
    <td>
      <code>
        0.07
      </code>
    </td>
    
    <td>
      Width of the bending edge, as a fraction of the shorter side.
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        blur
      </code>
    </td>
    
    <td>
      <code>
        number
      </code>
    </td>
    
    <td>
      <code>
        11
      </code>
    </td>
    
    <td>
      Softness of the transition from the bending edge to the calm center, in px.
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        brightness
      </code>
    </td>
    
    <td>
      <code>
        number
      </code>
    </td>
    
    <td>
      <code>
        50
      </code>
    </td>
    
    <td>
      Brightness of the calm center, in %. <code>
        50
      </code>
      
       leaves it undistorted.
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        opacity
      </code>
    </td>
    
    <td>
      <code>
        number
      </code>
    </td>
    
    <td>
      <code>
        0.93
      </code>
    </td>
    
    <td>
      How strongly the calm center holds the view still, from 0 to 1.
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        displace
      </code>
    </td>
    
    <td>
      <code>
        number
      </code>
    </td>
    
    <td>
      <code>
        0
      </code>
    </td>
    
    <td>
      Blurs the light coming through the glass, for a frosted look.
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        red-offset
      </code>
    </td>
    
    <td>
      <code>
        number
      </code>
    </td>
    
    <td>
      <code>
        0
      </code>
    </td>
    
    <td>
      Extra bending for the red channel. Different offsets split light into colored fringes.
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        green-offset
      </code>
    </td>
    
    <td>
      <code>
        number
      </code>
    </td>
    
    <td>
      <code>
        10
      </code>
    </td>
    
    <td>
      Extra bending for the green channel.
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        blue-offset
      </code>
    </td>
    
    <td>
      <code>
        number
      </code>
    </td>
    
    <td>
      <code>
        20
      </code>
    </td>
    
    <td>
      Extra bending for the blue channel.
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        background-opacity
      </code>
    </td>
    
    <td>
      <code>
        number
      </code>
    </td>
    
    <td>
      <code>
        0
      </code>
    </td>
    
    <td>
      Opacity of a soft frosted tint over the glass, from 0 to 1.
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        saturation
      </code>
    </td>
    
    <td>
      <code>
        number
      </code>
    </td>
    
    <td>
      <code>
        1
      </code>
    </td>
    
    <td>
      Saturation of what is seen through the glass.
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        x-channel
      </code>
    </td>
    
    <td>
      <code>
        'R' | 'G' | 'B' | 'A'
      </code>
    </td>
    
    <td>
      <code>
        'R'
      </code>
    </td>
    
    <td>
      Channel of the displacement map that moves pixels sideways.
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        y-channel
      </code>
    </td>
    
    <td>
      <code>
        'R' | 'G' | 'B' | 'A'
      </code>
    </td>
    
    <td>
      <code>
        'G'
      </code>
    </td>
    
    <td>
      Channel of the displacement map that moves pixels up and down.
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        mix-blend-mode
      </code>
    </td>
    
    <td>
      <code>
        GlassSurfaceBlendMode
      </code>
    </td>
    
    <td>
      <code>
        'difference'
      </code>
    </td>
    
    <td>
      How the two edge gradients of the displacement map are combined.
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        class
      </code>
    </td>
    
    <td>
      <code>
        string
      </code>
    </td>
    
    <td>
      —
    </td>
    
    <td>
      Extra classes for the glass.
    </td>
  </tr>
</tbody>
</table>

## Slots

<table>
<thead>
  <tr>
    <th>
      Slot
    </th>
    
    <th>
      Description
    </th>
  </tr>
</thead>

<tbody>
  <tr>
    <td>
      <code>
        default
      </code>
    </td>
    
    <td>
      Content shown on the glass.
    </td>
  </tr>
</tbody>
</table>

## Accessibility

- The filter SVG, WebGL canvas and outline layers are `aria-hidden`; only the slot content is exposed.
- The glass is decorative; put real buttons or links inside it for interaction.
- The WebGL renderer pauses while the glass is offscreen or the tab is hidden, and only redraws when the glass, the backdrop or a prop changes.
- Ported from [React Bits](https://reactbits.dev/components/glass-surface) by David Haz.
