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.
Crafting a custom Dynamic Shadow
Section titled “Crafting a custom Dynamic Shadow”The steps for overriding the built-in dynamic shadow are;
-
Enable
shadowso the container emits the necessary CSS variables. -
Write your desired shadow code using CSS
calc()withvar(--shadow-x)/var(--shadow-y). (see below) -
Assign it to
--hover-tilt-custom-shadowthrough a CSS class.
Examples
Section titled “Examples”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.
<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><hover-tilt class="simple-card" shadow scaleFactor={1.1} glareIntensity={0.3}> <div class="demo-card"> Simple Custom Shadow </div></hover-tilt>
<style> /* access the .simple-card with the ::part(container) Web Component selector */ .simple-card::part(container) { /* 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><HoverTilt class="neon-card" shadow scaleFactor={1.1} glareIntensity={0.3}> <div class="demo-card"> Neon Underglow </div></HoverTilt>
<style> /* access the .neon-card with the :global() svelte selector */ :global(.neon-card) { /* define the custom shadow */ --hover-tilt-custom-shadow: hsl(322 87% 56% / calc(var(--hover-tilt-opacity, 0) * 0.41 + 0.05)) calc(var(--shadow-x) * 2px) calc(var(--shadow-y) * 4px + 1px) 0px 2px, hsl(322 87% 56% / calc(var(--hover-tilt-opacity, 0) * 0.31 + 0.05)) calc(var(--shadow-x) * 4px) calc(var(--shadow-y) * 8px + 2px) 0px 4px, hsl(322 87% 56% / calc(var(--hover-tilt-opacity, 0) * 0.21 + 0.05)) calc(var(--shadow-x) * 6px) calc(var(--shadow-y) * 12px + 3px) 0px 6px, hsl(322 87% 56% / calc(var(--hover-tilt-opacity, 0) * 0.11 + 0.05)) calc(var(--shadow-x) * 8px) calc(var(--shadow-y) * 16px + 4px) 0px 8px, hsl(322 87% 56% / calc(var(--hover-tilt-opacity, 0) * 0.05 + 0.05)) calc(var(--shadow-x) * 10px) calc(var(--shadow-y) * 20px + 5px) 0px 10px; }</style><hover-tilt class="neon-card" shadow scaleFactor={1.1} glareIntensity={0.3}> <div class="demo-card"> Neon Underglow </div></hover-tilt>
<style> .neon-card::part(container) { --hover-tilt-custom-shadow: hsl(322 87% 56% / calc(var(--hover-tilt-opacity, 0) * 0.41 + 0.05)) calc(var(--shadow-x) * 2px) calc(var(--shadow-y) * 4px + 1px) 0px 2px, hsl(322 87% 56% / calc(var(--hover-tilt-opacity, 0) * 0.31 + 0.05)) calc(var(--shadow-x) * 4px) calc(var(--shadow-y) * 8px + 2px) 0px 4px, hsl(322 87% 56% / calc(var(--hover-tilt-opacity, 0) * 0.21 + 0.05)) calc(var(--shadow-x) * 6px) calc(var(--shadow-y) * 12px + 3px) 0px 6px, hsl(322 87% 56% / calc(var(--hover-tilt-opacity, 0) * 0.11 + 0.05)) calc(var(--shadow-x) * 8px) calc(var(--shadow-y) * 16px + 4px) 0px 8px, hsl(322 87% 56% / calc(var(--hover-tilt-opacity, 0) * 0.05 + 0.05)) calc(var(--shadow-x) * 10px) calc(var(--shadow-y) * 20px + 5px) 0px 10px; }</style>Breakdown
Section titled “Breakdown”Here’s a breakdown of a simple custom shadow, with each part and how it works.
-
First we need to call the component and apply the
shadowprop to enable the dynamic shadow.<HoverTilt class="simple-card" shadow> ... </HoverTilt><hover-tilt class="simple-card" shadow> ... </hover-tilt>This tells the component to render the shadow layer and emit the necessary CSS variables.
-
Then we need to create a CSS selector to target the container of the
<HoverTilt />component.:global(.simple-card) {/* ... */}.simple-card::part(container) {/* ... */}We target the container of the
<HoverTilt />component with the:global()selector in Svelte, or the::part(container)selector in Web Component. -
Next we assign the custom shadow to the
--hover-tilt-custom-shadowCSS variable.:global(.simple-card) {--hover-tilt-custom-shadow: /* ... */;}.simple-card::part(container) {--hover-tilt-custom-shadow: /* ... */;}This variable will be used to override the default shadow.
-
To make the shadow move around dynamically based on the tilt effect, we use the
--shadow-xand--shadow-yvariables./***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-xand--shadow-yvariables will be0, so a shadow offset ofcalc(0 * 10px)will be0px.But if the user tilts in the negative
xdirection, then--shadow-xwill be-1, so a shadow offset ofcalc(-1 * 10px)will be-10px. Likewise when tilted in the positivexdirection, then--shadow-xwill be1, so a shadow offset ofcalc(1 * 10px)will be10px. -
Lastly we have access to the
--hover-tilt-opacityvariable, which is a value between0and1that 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 a0.7 (20% + 50%)opacity when the component is active.
Complete beakdown code
Section titled “Complete beakdown code”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));}.simple-card::part(container) { --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.
List of variables available
Section titled “List of variables available”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!
Settable by you
Section titled “Settable by you”You can set these variables to override the default shadow with your own shadow.
| Variable | Description |
|---|---|
--hover-tilt-custom-shadow | Set this to override the default shadow with your own shadow. |
Set by the component
Section titled “Set by the component”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!
| Variable | Description |
|---|---|
--shadow-x | The current horizontal offset of the shadow based on the tilt effect. |
--shadow-y | The current vertical offset of the shadow based on the tilt effect. |
--shadow-y | The current vertical offset of the shadow based on the tilt effect. |
--hover-tilt-shadow-blur | Matches the shadowBlur prop. |
--hover-tilt-opacity | The opacity of the shadow based on whether the component is active or not. |
--hover-tilt-scale | The current scale of the component. |
See all the positional variables available in the Options → CSS page.