I want to create bi-colored pills like those used by e.g. gitlab scoped labels .
This can be done in the following way (heavily based on tidy implementation) from @Mc-Zen : Typst
However, as shown in the document, such pills do not like to be very long, and they mess up line breaks if they are.
Basically, I need a way to split a horizontal box
over multiple lines.
I tried to find a simple solution that would not require complex context calculations, but I failed.
Any idea?
Mc-Zen
April 14, 2025, 8:00am
2
Maybe you could achieve what you want with highlight
. A nice solution would be
#let pill(a, b, fill: red) = {
highlight(
fill: fill,
stroke: fill,
radius: (left: 100pt),
text(white, [~#a~])
)
highlight(
fill: white,
stroke: fill,
radius: (right: 100%),
text(fill, [~#b~])
)
}
Unfortunately, after a linebreak, the rounded corners are repeated
At least it’s already much better than using boxes. You could trick around a bit by adding the rounded beginning (and also end but here I did only the beginning) separately:
#let pill(a, b, fill: red) = {
highlight(
fill: fill,
stroke: fill,
radius: (left: 100pt),
[~~~~~]
)
h(-.7em)
highlight(
fill: fill,
stroke: fill,
text(white, [#a~])
)
highlight(
fill: white,
stroke: fill,
radius: (right: 100%),
text(fill, [~#b~])
)
}
This is something you might be able to tune until it looks good.
4 Likes
Mc-Zen:
#let pill(a, b, fill: red) = {
highlight(
fill: fill,
stroke: fill,
radius: (left: 100pt),
[~~~~~]
)
h(-.7em)
highlight(
fill: fill,
stroke: fill,
text(white, [#a~])
)
highlight(
fill: white,
stroke: fill,
radius: (right: 100%),
text(fill, [~#b~])
)
}
Thanks for that!
However it still breaks if it’s the rightmost part to go to a new line:
But I’ll surely find some more time to play around it and see if I can find a possible solution based on highlight
instead of box
Mc-Zen
April 14, 2025, 8:54pm
4
I know, I was just giving the idea based on the left part but you can do the same with the right one, I think.
Mc-Zen
April 14, 2025, 10:13pm
5
Note that highlight.stroke
accepts a dictionary which you can use like this
#let pill(a, b, fill: red) = {
highlight(
fill: fill,
stroke: fill,
radius: (left: 100%),
[~~~~~]
)
h(-.7em)
highlight(
fill: fill,
stroke: fill,
text(white, [#a~])
)
highlight(
fill: none,
stroke: (y: fill),
text(fill, [~#b])
)
h(-.7em)
highlight(
fill: none,
stroke: (y: fill, right: fill),
radius: (right: 100%),
text(fill, [~~~~~])
)
}
Still not perfect maybe but betterer.
4 Likes
Getting there!
#let pill(a, b, fill: gray, radius: 100%) = {
highlight(
fill: fill,
stroke: fill,
radius: (left: radius),
[~~~~~#sym.zws]
)
h(-.7em)
highlight(
fill: fill,
stroke: fill,
text(white, hyphenate: true, [#a~#sym.zws])
)
highlight(
fill: none,
stroke: (y: fill),
text(hyphenate: true)[#sym.zws~#b]
)
h(-.7em)
highlight(
fill: none,
stroke: (y: fill, right: fill),
radius: (right: radius),
text(fill, [~~~~~])
)
}
#box(width: 9em)[
#pill("definitely good even when")[it goes to new line!]
]
#box(width: 9em)[
#pill("first")[line!]\
#pill("second")[line! All good!]
#pill("thirddddddddddddd")[line! All good!]
#pill("fourth")[line! Less good!]
]
I had to also add a few sym.zws
to force it to go to new lines and not mess it up trying to connect different highlights.
It produces:
As you can see the last pill
is not great, but it is almost there. Any brilliant idea on that one?
flokl
April 15, 2025, 7:28pm
7
For the last one you could add a word joiner sym.wj
between the h
and the last highlight
to force the last word to stick to the rounded ending.
3 Likes
Indeed that works well!
#let pill(a, b, fill: gray, radius: 100%) = {
highlight(
fill: fill,
stroke: fill,
radius: (left: radius),
[#sym.zws~~~~~#sym.wj]
)
h(-.7em)
highlight(
fill: fill,
stroke: fill,
text(white, hyphenate: true, [#sym.wj#a~#sym.zws])
)
highlight(
fill: none,
stroke: (y: fill),
text(hyphenate: true)[#sym.zws~#b#sym.wj]
)
h(-.7em)
highlight(
fill: none,
stroke: (y: fill, right: fill),
radius: (right: radius),
text(fill, [#sym.wj~~~~~#sym.zws])
)
}
#box(width: 9em)[
#pill("definitely good even when")[it goes to new line!]
]
#box(width: 9em)[
#pill("first")[line!]\
#pill("second")[line! All good!]
#pill("thirddddddddddddd")[line! All good!]
#pill("fourth")[line! Also good!]
#pill("fifthhhhhhhhhhhhhhh")[line! Indeed also good!]
]
Results in:
To me this is almost perfect!
3 Likes
Mc-Zen
April 16, 2025, 12:40pm
9
Good one! The word-joiner was what I was looking for but I didn’t find it because I had forgotten the name.
Andrew
April 16, 2025, 3:58pm
10
1 Like
I’m still playing around with this to improve the final look and make it more generic. I’ll post here when I have a final version.
But, a question to the wise… this seems hackish at best.
Is there no other way to do this?
I mean, if someone were to write a package to do this, which route would one take?
Andrew
April 16, 2025, 9:51pm
12
It is… hackish.
In fact if you start applying it it shows several limitations:
different radiuses, fonts and font sizes require manual tuning of the spacing to look decent
there are weird artifacts. Look at what happens if I use modified text for the left side of a pill:
Even modifying the highlight’s edges did not seem to fix this.
Andrew
April 19, 2025, 8:32pm
14
#pill[text текст テキスト][next]
#highlight(stroke: 1pt)[text текст テキスト]
Looks like
opened 12:36AM - 30 Oct 24 UTC
bug
text
### Description
I used the highlighting for some stuff, but when I thought of a… dding a stroke, this happened:
```typ
#let hl = highlight.with(stroke: red)
- #hl[package manager/some other text/here]
- #hl[package manager/менеджер пакетов/包管理器]
```

I guess it's hard with glyphs which background fill is different in size, but for glyphs with the monotone background size I think it's definitely not the desired effect.
Instead of 3 stroked boxes, there should be 1, or at least 2 instead.
### Reproduction URL
_No response_
### Operating system
Linux
### Typst version
- [X] I am using the latest version of Typst
1 Like
indeed, that’s probably what it is! Watching the issue, thanks!
1 Like