Box shadows for Typst.
Installation
Add the package to your Typst document:
#import "@preview/shadowed:0.4.0": shadow
Quick Start
#import "@preview/shadowed:0.4.0": shadow
#set par(justify: true)
#shadow(blur: 8pt, fill: rgb(89, 85, 101, 25%), radius: 4pt)[
#block(fill: white, radius: 4pt, inset: 12pt)[
#lorem(50)
]
]
Parameters
/// A box shadow.
/// -> content
#let shadow(
/// Whether to draw the shadow inside the box instead of outside.
///
/// -> bool
inset: false,
/// The horizontal offset.
/// -> length
dx: 0pt,
/// The vertical offset.
/// -> length
dy: 0pt,
/// How strong to blur the shadow.
///
/// Must be equal to or greater than 0pt.
///
/// -> length
blur: 0pt,
/// How far to spread the shadow.
///
/// Can be either:
/// - A length for a uniform spread.
///
/// - A dictionary: With a dictionary, the spread for each side can be set
/// individually.
/// The dictionary can contain the following keys in order of precedence:
/// - top: The spread of the top side.
/// - right: The spread of the right side.
/// - bottom: The spread of the bottom side.
/// - left: The spread of the left side.
/// - x: The spread of the left and right sides.
/// - y: The spread of the top and bottom sides.
/// - rest: The spread for all sides except those for which the dictionary
/// explicitly sets a size.
///
/// Negative values contract the shadow on the respective side.
///
/// -> length | dictionary
spread: 0pt,
/// How to fill the shadow.
///
/// Currently only supports linear or radial gradients.
///
/// -> color | gradient | none
fill: black,
/// How much to round the shadow's corners.
///
/// Can be either:
/// - A relative length for a uniform corner radius,
/// relative to the minimum of the width and height divided by two.
///
/// - A dictionary: With a dictionary, the stroke for each side can be set
/// individually.
/// The dictionary can contain the following keys in order of precedence:
/// - top-left: The top-left corner radius.
/// - top-right: The top-right corner radius.
/// - bottom-right: The bottom-right corner radius.
/// - bottom-left: The bottom-left corner radius.
/// - left: The top-left and bottom-left corner radii.
/// - top: The top-left and top-right corner radii.
/// - right: The top-right and bottom-right corner radii.
/// - bottom: The bottom-left and bottom-right corner radii.
/// - rest: The radii for all corners except those for which the dictionary
/// explicitly sets a size.
///
/// -> relative | dictionary
radius: 0pt,
/// The content to place in front of the shadow.
/// -> content
body,
) = { /* ... */ }
Examples
Inner Shadow
Set inset to true to draw the shadow inside the box:
#shadow(inset: true, spread: 2pt, blur: 4pt)[
#block(inset: 4pt)[
#text(size: 24pt)[
This box has an inner shadow
]
]
]
Multiple Shadows
You can apply multiple shadows to the same content by stacking them:
#shadow(spread: 2pt, blur: 4pt, fill: red)[
#shadow(inset: true, spread: 2pt, blur: 4pt, fill: blue)[
#block(inset: 4pt, fill: white)[
#text(size: 24pt)[
This box has an inner and outer shadow
]
]
]
]
Offset
Control the shadow position with dx and dy parameters:
// Shadow offset to the bottom-right
#shadow(dx: 3pt, dy: 3pt, blur: 4pt)[
#block(width: 100pt, height: 100pt, fill: white)
]
// Shadow offset to the top-left
#shadow(dx: -3pt, dy: -3pt, blur: 4pt)[
#block(width: 100pt, height: 100pt, fill: white)
]
Alternatively the offsets can be achieved by using an asymmetric spread dictionary instead of dx/dy:
// Shadow offset to the bottom-right
#shadow(blur: 4pt, spread: (top: -1pt, left: -1pt, right: 1pt, bottom: 1pt))[
#block(width: 100pt, height: 100pt, fill: white)
]
// Shadow offset to the top-left
#shadow(blur: 4pt, spread: (top: 1pt, left: 1pt, right: -1pt, bottom: -1pt))[
#block(width: 100pt, height: 100pt, fill: white)
]
Spread
The spread parameter controls how much the shadow expands or contracts:
// Positive spread makes the shadow larger
#shadow(blur: 4pt, spread: 2pt)[
#block(width: 100pt, height: 100pt, fill: white)
]
// Negative spread makes the shadow smaller
#shadow(blur: 4pt, spread: -2pt)[
#block(width: 100pt, height: 100pt, fill: white)
]
The spread for each side can be controlled individually with a dictionary:
// Spread only downwards
#shadow(blur: 4pt, spread: (bottom: 10pt))[
#block(width: 100pt, height: 100pt, fill: white)
]
// Spread to the right
#shadow(blur: 4pt, spread: (right: 10pt))[
#block(width: 100pt, height: 100pt, fill: white)
]
// Asymmetric spread
#shadow(blur: 4pt, spread: (top: 2pt, right: 8pt, bottom: 10pt, left: -4pt))[
#block(width: 100pt, height: 100pt, fill: white)
]
Layout
By default shadow does not affect the layout and just draws the shadow behind its content. This can lead to overlapping elements. To prevent this a pad of blur + spread can be applied.
#let blur = 4pt
#let spread = 2pt
// Padding has to consist of the blur + the spread.
#pad(blur + spread)[
#shadow(blur: blur, spread: spread)[
#block(height: 120pt, width: 120pt, fill: white)
]
]
For comparison, here are two shadows where one affects the layout while the other does not:
Fill
The fill of a shadow can consist of nothing, a solid color or a gradient. Currently only linear and radial gradients are supported.
// Solid color
#shadow(blur: 3pt, fill: color.teal)[
#block(width: 120pt, height: 120pt)
]
// Linear gradient
#shadow(blur: 3pt, fill: gradient.linear(..color.map.rainbow, angle: 45deg))[
#block(width: 120pt, height: 120pt)
]
// Radial gradient
#shadow(blur: 3pt, fill: gradient.radial(..color.map.plasma, center: (40%, 40%)))[
#block(width: 120pt, height: 120pt)
]
Custom Radius
You can set individual corner radii using a dictionary:
#let radius = (
top-left: 0pt,
top-right: 8pt,
bottom-right: 0pt,
bottom-left: 8pt,
)
#shadow(
blur: 4pt,
radius: radius,
)[
#block(
width: 100pt,
height: 100pt,
fill: white,
radius: radius,
)
]
License
shadowed is available under the MIT OR Apache-2.0 license. See LICENSE-MIT and LICENSE-APACHE for details.
Credits
This project was inspired by Harbinger.