Skip to content

Custom Shadow

When the shadow prop is enabled, <HoverTilt /> sets box-shadow on the tilt element using two CSS variables:

  • --hover-tilt-custom-shadow (optional override for anything you want)
  • --hover-tilt-default-shadow (fallback to the default drop shadow)

The shadow CSS looks something like this;

.hover-tilt-container {
--shadow-x: calc(var(--hover-tilt-x, 0.5) - 0.5); /* center the shadow horizontally */
--shadow-y: calc(var(--hover-tilt-y, 0.5) - 0.5); /* center the shadow vertically */
}
.hover-tilt-shadow {
--hover-tilt-default-shadow:
calc(var(--shadow-x) * var(--shadow-blur-1))
calc(var(--shadow-y) * var(--shadow-blur-1) / 2 + var(--shadow-blur-1) / 4) calc(var(--shadow-blur-1) / 2)
calc(var(--shadow-blur-1) * -0.25) lch(0% 0 0 / calc(var(--hover-tilt-opacity, 0) * 0.125)),
calc(var(--shadow-x) * var(--shadow-blur-2))
calc(var(--shadow-y) * var(--shadow-blur-2) / 2 + var(--shadow-blur-2) / 4) calc(var(--shadow-blur-2) / 2)
calc(var(--shadow-blur-2) * -0.25) lch(0% 0 0 / calc(var(--hover-tilt-opacity, 0) * 0.125));
/* we look for a custom shadow first, falling back to the default shadow */
box-shadow: var(--hover-tilt-custom-shadow, var(--hover-tilt-default-shadow));
}

--shadow-x and --shadow-y are used to center the shadow horizontally and vertically, and available to use in your custom shadow implementation to multiply the x/y shadow offsets with calc(). These values update as the pointer moves and the component tilts.

The steps for overriding the built-in dynamic shadow are;

  1. Enable shadow so the container emits the necessary CSS variables.

  2. Write your desired shadow code using CSS calc() with var(--shadow-x) / var(--shadow-y). (see below)

  3. Assign it to --hover-tilt-custom-shadow through a CSS class.

The key difference between the Svelte and Web Component examples is the selector used to target the custom shadow. In Svelte the :global() selector is needed to access the container, while in the Web Component the ::part(container) selector is used.

SimpleShadow.svelte
<HoverTilt class="simple-card" shadow scaleFactor={1.1} glareIntensity={0.3}>
<div class="demo-card">
Simple Custom Shadow
</div>
</HoverTilt>
<style>
/* access the .simple-card with the :global() svelte selector */
:global(.simple-card) {
/* define the custom shadow */
--hover-tilt-custom-shadow:
hsl(210 75% 15% / 0.4) calc(var(--shadow-x) * 4px) calc(var(--shadow-y) * 6px + 6px) calc(10px + var(--hover-tilt-opacity, 0) * 12px) 0px,
hsl(210 75% 15% / 0.4) calc(var(--shadow-x) * 4px) calc(var(--shadow-y) * 2px + 2px) calc(2px + var(--hover-tilt-opacity, 0) * 4px) 0px;
}
</style>

Here’s a breakdown of a simple custom shadow, with each part and how it works.

  1. First we need to call the component and apply the shadow prop to enable the dynamic shadow.

    <HoverTilt class="simple-card" shadow> ... </HoverTilt>

    This tells the component to render the shadow layer and emit the necessary CSS variables.

  2. Then we need to create a CSS selector to target the container of the <HoverTilt /> component.

    :global(.simple-card) {/* ... */}

    We target the container of the <HoverTilt /> component with the :global() selector in Svelte, or the ::part(container) selector in Web Component.

  3. Next we assign the custom shadow to the --hover-tilt-custom-shadow CSS variable.

    :global(.simple-card) {
    --hover-tilt-custom-shadow: /* ... */;
    }

    This variable will be used to override the default shadow.

  4. To make the shadow move around dynamically based on the tilt effect, we use the --shadow-x and --shadow-y variables.

    /***
    CSS box-shadow syntax:
    [horizontal offset] [vertical offset] [blur] [spread] [color]
    ***/
    --hover-tilt-custom-shadow:
    /*** [horizontal offset] [vertical offset] [blur] [spread] [color] ***/
    calc(var(--shadow-x) * 10px) calc(var(--shadow-y) * 10px + 5px) 5px 10px #ccc;
    /*** │ │ │ │
    │ └─ multiplied by 10px │ └─ multiplied by 10px, shift down by 5px
    └─ shadow-x offset ─────┘ └─ shadow-y offset ───────┘
    ***/

    When the component has no tilt applied (either inactive, or the user has perfect centering of the pointer), then the --shadow-x and --shadow-y variables will be 0, so a shadow offset of calc(0 * 10px) will be 0px.

    But if the user tilts in the negative x direction, then --shadow-x will be -1, so a shadow offset of calc(-1 * 10px) will be -10px. Likewise when tilted in the positive x direction, then --shadow-x will be 1, so a shadow offset of calc(1 * 10px) will be 10px.

  5. Lastly we have access to the --hover-tilt-opacity variable, which is a value between 0 and 1 that represents the opacity of the shadow based on whether the component is active or not.

    --hover-tilt-custom-shadow:
    /*** [horizontal offset] [vertical offset] [blur] [spread] [color] ***/
    calc(var(--shadow-x) * 10px) calc(var(--shadow-y) * 10px + 5px) 5px 10px hsl(0 0% 0% / calc(var(--hover-tilt-opacity, 0) * 0.5 + 0.2));
    /*** │ │ │ └──────────┐
    │ │ multiplied by 50%, add 20% extra ─┘
    │ └─ using tilt-opacity ───┘
    └─ a black shadow ────────────┘
    ***/

    This will give the shadow a 0.2 (20%) opacity when the component is inactive, and a 0.7 (20% + 50%) opacity when the component is active.

And so the final (basic) custom shadow implementation looks like this;

:global(.simple-card) {
--hover-tilt-custom-shadow:
calc(var(--shadow-x) * 10px) calc(var(--shadow-y) * 10px + 5px) 5px 10px
hsl(0 0% 0% / calc(var(--hover-tilt-opacity, 0) * 0.5 + 0.2));
}

This probably looks a little daunting and complex at first, but if you have experience with CSS calc() and var() functions, it should be relatively straightforward.

These variables are available to customise the shadow appearance / position.

They can also be used inside the content slot to customise the way your content reacts to the hover-tilt interactions!

You can set these variables to override the default shadow with your own shadow.

VariableDescription
--hover-tilt-custom-shadowSet this to override the default shadow with your own shadow.

The component will set these variables to control the shadow appearance / position. You may consume these in the --hover-tilt-custom-shadow variable.

The variables are exposed via ::slotted(*) so that they can be picked up by your slotted content to customise the way your content reacts to the hover-tilt interactions!

VariableDescription
--shadow-xThe current horizontal offset of the shadow based on the tilt effect.
--shadow-yThe current vertical offset of the shadow based on the tilt effect.
--shadow-yThe current vertical offset of the shadow based on the tilt effect.
--hover-tilt-shadow-blurMatches the shadowBlur prop.
--hover-tilt-opacityThe opacity of the shadow based on whether the component is active or not.
--hover-tilt-scaleThe current scale of the component.

See all the positional variables available in the Options → CSS page.