├── .gitignore
├── examples
├── colorbox.pdf
├── colorbox.png
├── stickybox.pdf
├── stickybox.png
├── outline-colorbox.pdf
├── outline-colorbox.png
├── slanted-colorbox.pdf
├── slanted-colorbox.png
├── colorbox.typ
├── slanted-colorbox.typ
├── outline-colorbox.typ
└── stickybox.typ
├── .vscode
└── settings.json
├── tape.svg
├── typst.toml
├── LICENSE
├── background.svg
├── README.md
└── lib.typ
/.gitignore:
--------------------------------------------------------------------------------
1 |
2 | .DS_Store
3 |
--------------------------------------------------------------------------------
/examples/colorbox.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lkoehl/typst-boxes/HEAD/examples/colorbox.pdf
--------------------------------------------------------------------------------
/examples/colorbox.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lkoehl/typst-boxes/HEAD/examples/colorbox.png
--------------------------------------------------------------------------------
/examples/stickybox.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lkoehl/typst-boxes/HEAD/examples/stickybox.pdf
--------------------------------------------------------------------------------
/examples/stickybox.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lkoehl/typst-boxes/HEAD/examples/stickybox.png
--------------------------------------------------------------------------------
/examples/outline-colorbox.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lkoehl/typst-boxes/HEAD/examples/outline-colorbox.pdf
--------------------------------------------------------------------------------
/examples/outline-colorbox.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lkoehl/typst-boxes/HEAD/examples/outline-colorbox.png
--------------------------------------------------------------------------------
/examples/slanted-colorbox.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lkoehl/typst-boxes/HEAD/examples/slanted-colorbox.pdf
--------------------------------------------------------------------------------
/examples/slanted-colorbox.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lkoehl/typst-boxes/HEAD/examples/slanted-colorbox.png
--------------------------------------------------------------------------------
/.vscode/settings.json:
--------------------------------------------------------------------------------
1 | {
2 | "typst-lsp.experimentalFormatterMode": "on",
3 | "typst-lsp.exportPdf": "never"
4 | }
5 |
--------------------------------------------------------------------------------
/examples/colorbox.typ:
--------------------------------------------------------------------------------
1 | #import "../lib.typ": colorbox
2 |
3 | #set page(paper: "a4", margin: 0.5cm, height: auto)
4 |
5 | #colorbox(title: lorem(5), color: "blue")[
6 | #lorem(50)
7 | ]
--------------------------------------------------------------------------------
/examples/slanted-colorbox.typ:
--------------------------------------------------------------------------------
1 | #import "../lib.typ": slanted-colorbox
2 |
3 | #set page(paper: "a4", margin: 0.5cm, height: auto)
4 |
5 | #slanted-colorbox(title: lorem(5), color: "sand")[
6 | #lorem(50)
7 | ]
--------------------------------------------------------------------------------
/tape.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/examples/outline-colorbox.typ:
--------------------------------------------------------------------------------
1 | #import "../lib.typ": outline-colorbox, colorbox
2 |
3 | #set page(paper: "a4", margin: 0.5cm, height: auto)
4 |
5 | #outline-colorbox(title: lorem(5), color: "gray")[
6 | #lorem(50)
7 | ]
8 |
9 | #outline-colorbox(title: lorem(5), centering: true, color: (fill: green.lighten(70%), stroke: green.darken(40%)))[
10 | #lorem(50)
11 | ]
--------------------------------------------------------------------------------
/typst.toml:
--------------------------------------------------------------------------------
1 | [package]
2 | name = "colorful-boxes"
3 | version = "1.4.3"
4 | repository = "https://github.com/lkoehl/typst-boxes"
5 | entrypoint = "lib.typ"
6 | exclude = ["/examples/*"]
7 | authors = ["Lukas Köhl "]
8 | license = "MIT"
9 | description = "Predefined colorful boxes to spice up your document"
10 | categories = ["components"]
11 | keywords = ["colorbox", "boxes"]
12 |
--------------------------------------------------------------------------------
/examples/stickybox.typ:
--------------------------------------------------------------------------------
1 | #import "../lib.typ": stickybox
2 |
3 | #set page(width: auto, margin: 0.5cm, height: auto)
4 |
5 | #grid(
6 | columns: 3 * (auto,), gutter: 3em,
7 | stickybox(width: 5cm, rotation: 5deg)[
8 | #lorem(20)
9 | ],
10 |
11 | stickybox(width: 5cm, rotation: -5deg, fill: rgb("#ffb6a6"))[
12 | #lorem(20)
13 | ],
14 |
15 | stickybox(
16 | width: 5cm,
17 | fill: rgb("#d4ffdc"),
18 | tape: false,
19 | )[
20 | #lorem(20)
21 | ],
22 | )
23 |
24 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2023 lkoehl
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/background.svg:
--------------------------------------------------------------------------------
1 |
2 |
19 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Typst Boxes
2 |
3 | **Typst Boxes** is a Typst package for adding colorful, customizable boxes to your documents.
4 |
5 | [➡️ Explore the live example project](https://typst.app/project/rp9q3upfc69bPUCbv0BjzX)
6 |
7 | ---
8 |
9 | ## Table of Contents
10 |
11 | 1. [Features](#features)
12 | 2. [Examples & Usage](#examples--usage)
13 | - [Colorbox](#colorbox)
14 | - [Slanted Colorbox](#slanted-colorbox)
15 | - [Outline Colorbox](#outline-colorbox)
16 | - [Stickybox](#stickybox)
17 | 3. [Contributing](#contributing)
18 | 4. [License](#license)
19 |
20 | ---
21 |
22 | ## Features
23 |
24 | - Predefined color themes: **black**, **red**, **blue**, **green**, **purple**, **gold**, and more
25 | - `slanted-colorbox` for angled headlines
26 | - `outline-colorbox` for simple bordered layouts
27 | - `stickybox` for rotatable notes
28 | - Custom color support via `color` parameter (string or dictionary)
29 | - Disable sticky tape with `tape: false`
30 |
31 | ---
32 |
33 | ## Examples & Usage
34 |
35 | ### Colorbox
36 |
37 | 
38 |
39 | ```typst
40 | #colorbox(
41 | title: lorem(5),
42 | color: "blue",
43 | radius: 2pt,
44 | width: auto,
45 | )[
46 | #lorem(50)
47 | ]
48 | ```
49 |
50 | Use other built‑in colors:
51 | `"red"`, `"green"`, `"purple"`, `"gold"`, `"gray"`, `"cyan"`, etc.
52 |
53 | Custom color via dictionary:
54 |
55 | ```typst
56 | #colorbox(
57 | title: "Custom Theme",
58 | color: (
59 | fill: rgb("#f0f8ff"),
60 | stroke: rgb("#00bfff"),
61 | title: rgb("#002366")
62 | ),
63 | radius: 4pt,
64 | width: auto
65 | )[
66 | "This box uses a custom color dictionary."
67 | ]
68 | ```
69 |
70 | ---
71 |
72 | ### Slanted Colorbox
73 |
74 | 
75 |
76 | ```typst
77 | #slanted-colorbox(
78 | title: lorem(5),
79 | color: "red",
80 | radius: 0pt,
81 | width: auto,
82 | )[
83 | #lorem(50)
84 | ]
85 | ```
86 |
87 | ---
88 |
89 | ### Outline Colorbox
90 |
91 | 
92 |
93 | ```typst
94 | #outline-colorbox(
95 | title: lorem(5),
96 | width: auto,
97 | radius: 2pt,
98 | centering: false,
99 | )[
100 | #lorem(50)
101 | ]
102 |
103 | #outline-colorbox(
104 | title: lorem(5),
105 | color: "green",
106 | width: auto,
107 | radius: 2pt,
108 | centering: true,
109 | )[
110 | #lorem(50)
111 | ]
112 | ```
113 |
114 | Custom colors via dictionary:
115 |
116 | ```typst
117 | #outline-colorbox(
118 | title: "Soft Green",
119 | color: (
120 | fill: green.lighten(70%),
121 | stroke: green.darken(40%)
122 | ),
123 | width: auto,
124 | radius: 2pt,
125 | centering: false,
126 | )[
127 | #lorem(20)
128 | ]
129 | ```
130 |
131 | ---
132 |
133 | ### Stickybox
134 |
135 | 
136 |
137 | #### Basic
138 |
139 | ```typst
140 | #stickybox(
141 | rotation: 5deg,
142 | width: 5cm,
143 | )[
144 | #lorem(20)
145 | ]
146 | ```
147 |
148 | #### Custom Background Color
149 |
150 | ```typst
151 | #stickybox(
152 | width: 5cm,
153 | rotation: -5deg,
154 | fill: rgb("#ffb6a6"),
155 | )[
156 | #lorem(20)
157 | ]
158 | ```
159 |
160 | #### Disable Tape
161 |
162 | ```typst
163 | #stickybox(
164 | width: 8cm,
165 | fill: rgb("#33ff57"),
166 | tape: false,
167 | )[
168 | "No tape here!"
169 | ]
170 | ```
171 |
172 | ---
173 |
174 | ## Contributing
175 |
176 | We welcome contributions!
177 | 1. Fork this repository
178 | 2. Create a feature branch
179 | 3. Open a pull request with a clear description
180 |
181 | ---
182 |
183 | ## License
184 |
185 | This project is licensed under the **MIT License**. See [LICENSE](LICENSE) for details.
186 |
--------------------------------------------------------------------------------
/lib.typ:
--------------------------------------------------------------------------------
1 | #import "@preview/showybox:2.0.3": showybox
2 |
3 | #let box-colors = (
4 | default: (stroke: luma(70), fill: white, title: white),
5 | red: (stroke: rgb(237, 32, 84), fill: rgb(253, 228, 224), title: white),
6 | green: (stroke: rgb(102, 174, 62), fill: rgb(235, 244, 222), title: white),
7 | blue: (stroke: rgb(29, 144, 208), fill: rgb(232, 246, 253), title: white),
8 | purple: (stroke: rgb(137, 89, 168), fill: rgb(230, 217, 243), title: white),
9 | gray: (stroke: rgb(158, 158, 158), fill: rgb(245, 245, 245), title: white),
10 | cyan: (stroke: rgb(0, 188, 212), fill: rgb(224, 247, 250), title: white),
11 | teal: (stroke: rgb(0, 150, 136), fill: rgb(224, 242, 241), title: white),
12 | indigo: (stroke: rgb(63, 81, 181), fill: rgb(232, 234, 246), title: white),
13 | gold: (stroke: rgb(212, 175, 55), fill: rgb(255, 247, 207), title: white),
14 | lavender: (stroke: rgb(150, 123, 182), fill: rgb(238, 230, 250), title: white),
15 | sand: (stroke: rgb(194, 178, 128), fill: rgb(245, 238, 222), title: white),
16 | )
17 |
18 | #let colorbox(
19 | title: none,
20 | box-colors: box-colors,
21 | color: "default",
22 | radius: 2pt,
23 | inset: 8pt,
24 | stroke: 2pt,
25 | width: auto,
26 | body,
27 | ) = {
28 | let stroke-color = black
29 | let fill-color = gray.lighten(50%)
30 | let title-color = white
31 | if type(color) == str {
32 | stroke-color = box-colors.at(color, default:(stroke: black)).stroke
33 | fill-color = box-colors.at(color, default:(fill: gray.lighten(50%))).fill
34 | } else if type(color) == dictionary {
35 | stroke-color = color.at("stroke", default: stroke-color)
36 | fill-color = color.at("fill", default: fill-color)
37 | title-color = color.at("text", default: title-color)
38 | }
39 | if title != none {
40 | return showybox(
41 | title: title,
42 | breakable: true,
43 | frame: (
44 | title-color: stroke-color,
45 | body-color: fill-color,
46 | border-color: stroke-color,
47 | radius: radius,
48 | thickness: stroke,
49 | body-inset: (top: inset + 4pt, rest: inset),
50 | ),
51 | title-style: (
52 | color: title-color,
53 | weight: "bold",
54 | boxed-style: (
55 | anchor: (
56 | x: left,
57 | y: top,
58 | ),
59 | offset: (
60 | x: -1em,
61 | ),
62 | radius: (top-left: radius, bottom-right: radius),
63 | ),
64 | ),
65 | body-style: (
66 | align: left,
67 | color: black,
68 | ),
69 | width: width,
70 | )[
71 | #body
72 | ]
73 | } else {
74 | return showybox(
75 | breakable: true,
76 | frame: (
77 | title-color: stroke-color,
78 | body-color: fill-color,
79 | border-color: stroke-color,
80 | radius: radius,
81 | thickness: stroke,
82 | body-inset: inset,
83 | ),
84 | title-style: (
85 | color: title-color,
86 | weight: "bold",
87 | boxed-style: (
88 | anchor: (
89 | x: left,
90 | y: top,
91 | ),
92 | offset: (
93 | x: -1em,
94 | ),
95 | radius: (radius),
96 | ),
97 | ),
98 | body-style: (
99 | align: left,
100 | color: black,
101 | ),
102 | width: width,
103 | )[
104 | #body
105 | ]
106 | }
107 | }
108 |
109 | #let colorbox-rounded(
110 | title: none,
111 | box-colors: box-colors,
112 | color: "default",
113 | radius: 2pt,
114 | inset: 8pt,
115 | stroke: 2pt,
116 | width: auto,
117 | body,
118 | ) = {
119 | let stroke-color = black
120 | let fill-color = gray.lighten(50%)
121 | let title-color = white
122 | if type(color) == str {
123 | stroke-color = box-colors.at(color, default:(stroke: black)).stroke
124 | fill-color = box-colors.at(color, default:(fill: gray.lighten(50%))).fill
125 | } else if type(color) == dictionary {
126 | stroke-color = color.at("stroke", default: stroke-color)
127 | fill-color = color.at("fill", default: fill-color)
128 | title-color = color.at("text", default: title-color)
129 | }
130 | showybox(
131 | title: title,
132 | breakable: true,
133 | frame: (
134 | title-color: stroke-color,
135 | body-color: fill-color,
136 | border-color: stroke-color,
137 | radius: radius,
138 | thickness: stroke,
139 | body-inset: inset,
140 | ),
141 | title-style: (
142 | color: title-color,
143 | weight: "bold",
144 | boxed-style: (
145 | anchor: (
146 | x: left,
147 | y: top,
148 | ),
149 | offset: (
150 | x: -1em,
151 | ),
152 | radius: (radius),
153 | ),
154 | ),
155 | body-style: (
156 | align: left,
157 | color: black,
158 | ),
159 | width: width,
160 | )[
161 | #body
162 | ]
163 | }
164 |
165 | #let slanted-colorbox(
166 | title: "Title",
167 | box-colors: box-colors,
168 | color: "default",
169 | radius: 2pt,
170 | stroke: 2pt,
171 | inset: 8pt,
172 | width: auto,
173 | body,
174 | ) = {
175 | let stroke-color = black
176 | let fill-color = gray.lighten(50%)
177 | let title-color = white
178 | if type(color) == str {
179 | stroke-color = box-colors.at(color, default:(stroke: black)).stroke
180 | fill-color = box-colors.at(color, default:(fill: gray.lighten(50%))).fill
181 | } else if type(color) == dictionary {
182 | stroke-color = color.at("stroke", default: stroke-color)
183 | fill-color = color.at("fill", default: fill-color)
184 | title-color = color.at("text", default: title-color)
185 | }
186 | showybox(
187 | breakable: true,
188 | frame: (
189 | body-color: fill-color,
190 | border-color: stroke-color,
191 | radius: radius,
192 | thickness: stroke,
193 | body-inset: 0pt,
194 | ),
195 | width: width,
196 | )[
197 | #context {
198 | let size = measure(title)
199 | let inset = 8pt
200 | polygon(
201 | fill: stroke-color,
202 | (0pt, 0pt),
203 | (0pt, size.height + (2 * inset)),
204 | (size.width * 1.11 + (2 * inset), size.height + (2 * inset)),
205 | (size.width * 1.11 + (2 * inset) + 6pt, 0cm),
206 | )
207 | }
208 | #place(left + top, dx: 10pt, dy: 7pt)[
209 | #text(fill: white, weight: "bold")[#title]
210 | ]
211 | #block(inset: (top: inset - 10pt, rest: inset))[
212 | #body
213 | ]
214 | ]
215 | }
216 |
217 |
218 | #let outline-colorbox(
219 | title: "Title",
220 | box-colors: box-colors,
221 | color: "default",
222 | width: 100%,
223 | radius: 2pt,
224 | stroke: 2pt,
225 | inset: 8pt,
226 | centering: false,
227 | body,
228 | ) = {
229 | let stroke-color = black
230 | let fill-color = gray.lighten(50%)
231 | let title-color = white
232 | if type(color) == str {
233 | stroke-color = box-colors.at(color, default:(stroke: black)).stroke
234 | fill-color = box-colors.at(color, default:(fill: gray.lighten(50%))).fill
235 | } else if type(color) == dictionary {
236 | stroke-color = color.at("stroke", default: stroke-color)
237 | fill-color = color.at("fill", default: fill-color)
238 | title-color = color.at("text", default: title-color)
239 | }
240 | return showybox(
241 | title: text(fill: white, weight: "bold")[#title],
242 | breakable: true,
243 | frame: (
244 | title-color: stroke-color,
245 | body-color: fill-color,
246 | border-color: stroke-color,
247 | radius: radius,
248 | thickness: stroke,
249 | body-inset: inset,
250 | ),
251 | title-style: (
252 | color: title-color,
253 | weight: "bold",
254 | boxed-style: (
255 | anchor: (
256 | x: if centering { center } else { left },
257 | y: horizon,
258 | ),
259 | offset: (
260 | x: if centering { 0em } else { 1em },
261 | ),
262 | radius: (radius),
263 | ),
264 | ),
265 | width: width,
266 | )[
267 | #body
268 | ]
269 | }
270 |
271 | #let stickybox(rotation: 0deg, width: 100%, fill: rgb(255, 240, 172), tape: true, body) = {
272 | return rotate(rotation)[
273 | #block(width: width)[
274 | #layout(size => {
275 | let height = measure(image("background.svg", width: size.width)).height
276 | place(
277 | bottom + center,
278 | dy: 0.6 * height,
279 | )[
280 | #image("background.svg", width: size.width)
281 | ]
282 | box(
283 | fill: fill,
284 | width: width,
285 | inset: (top: if tape { 12pt } else { 8pt }, x: 8pt, bottom: 8pt),
286 | )[
287 | #body
288 | #if tape {
289 | place(
290 | top + center,
291 | dy: -2mm - 12pt,
292 | )[
293 | #image(
294 | "tape.svg",
295 | width: if type(width) == ratio { calc.clamp(width * 0.35cm / 1cm, 1, 4) * 1cm } else {
296 | calc.clamp(width * 0.35 / 1cm, 1, 4) * 1cm
297 | },
298 | height: 4mm,
299 | )
300 | ]
301 | }
302 | ]
303 | })
304 | ]
305 | ]
306 | }
307 |
--------------------------------------------------------------------------------