CSS for images side by side

Very often, I need to put the images side by side in my Markdown document. Sometimes, just two images side by side, sometimes three, sometimes four. Currently, I use manually put width=50%, 33%, 25% respectively to achieve that.

![Image 2024-11-17 134505-753515105.249340](x-devonthink-item://BB5288D2-20D2-4DA8-B4EA-4F47FD471B70 width="50%“)![Image 2024-11-17 134525-753515125.352287](x-devonthink-item://0FD80553-9B4F-4C58-ABE8-0B66493A4B94 width="50%”)

I was wondering is this achievable dynamically using CSS? Say, if I put two images side by side, it will use width 50%; if I put three images side by side, it will use width 33%; if I put four images side by side, it will use width 25%?

Interesting challenge – also because you omitted important details :wink: For this example, let’s assume that you have empty lines before and after your images and not embedded the two images in a paragraph of text.

Like that

…last line of previous paragraph

![](image1)![](image2)

First line of next paragraph

In that case, the generated HTML looks like this

<p>
  <img>
  <img>
</p>

In former times, you’d probably have used a float on the second image. Not in 2024, though – we have display: grid now. So


p:has(img:nth-child(2)) {
  display: grid;
  grid-template-columns: 1fr 1fr;
  grid-gap: 0.5rem;
}

gets you started: Every p element containing two img elements (p:has(img:nth-child(2))) gets a display: grid with two equally wide columns and a gap between them of 0.5rem.

There’s a very helpful blog post on child-counting:

Note the 1fr dimension for the grid-template-columns here: That distributes the free space evenly, i.e. withouth the grid-gap. If you use 50% instead of 1fr, the whole grid will be 0.5rem wider than the rest of the document.

Now it’s getting more interesting. If you use ![](... width="50%") in your MD document, your image width will be 50% of – the parent’s width. And you can’t do anything about it, because in the generated HTML you have <img ... style="width:50%"> – embedded styles override everything in a CSS.

The parent of your img is now a grid cell (roughly) 50% of the total document width. Which will make your images only a quarter as wide as the document. Not what you want, I presume.

Therefore, I’d take another approach.

  • Either, you do not set the width of your two images at all in your MD and rely on the CSS to handle paragraphs with two images correctly.
  • or, you append a pseudo query string like ![](...?width=half) to your img URL and use a more specific selector in your CSS, like
    p:has(img[src$="width=half"]:nth-child(2))

Personally, I’d go for the first approach: less typing. And if you apply the same approach to three or four images by adding rules like

p:has(img:nth-child(3)) {
  display: grid
  grid-template-columns: repeat(3, 1fr);
  grid-gap: 0.2rem
}

etc, you can simply determine the outcome by how many images you put in a single paragraph (that is the conditio sine qua non: all images must be in the same line in the MD with an empty line before and after them).

Just take care of the ordering in the CSS: the rules must come in increasing number of children – first 2, then 3 and 4.

Edit
Well, I finished too early, and CSS is way cooler than one tends to think. There’s no need to fiddle around with the number of columns at all:

p:has(img:nth-child(2)) {
  grid-auto-columns: 1fr;
  grid-auto-flow: column;
  grid-gap: 0.2rem;
}

does everything automagically if it sees at least two images in a paragraph. A single rule to rule them all.

5 Likes

Thanks. It works perfectly, in DT. (The inline style element is for consistent testing in DT and DTTG)

However, It doesn’t display side by side in DTTG. Do I need to do anything differently in DTTG?

Why would you want it to in DEVONthink To Go, especially in portrait orientation?

I don’t know. Possibly because DTTG uses a different internal stylesheet than DT.
But as one can’t convert MD to HTML in DTTG, that’s difficult to debug.

Edit

It seems to work ok with this inline style:

<style>
p:has(img:nth-child(2)) {
  display: grid;
  grid-template-columns: 50% 50%;
  grid-gap: 0.5rem;
  img {
    width: 100%;
  }
}
</style>

Without setting the width on img, the images overflow the grid. I don’t have the time to figure out why.

Thanks for your reply. Appreciate your effort.

Sorry, It was a mistake of my part. I forgot to remove the customised css settings in DT.

After I remove the css settings in DT and purely using just the inline style element, the order of the element becomes important. if “img” element is before the “p” element, it wont’ work for both DT and DTTG.

However, if I move the “img” element after the “p” element (as below), it works for both DT and DTTG. Could this be due to the CSS Specificity rule?

<style>
p:has(img:nth-child(2)) {
  display: grid; 
  grid-auto-columns: 1fr;
  grid-auto-flow: column;
  grid-gap: 0.2rem;
}

img {
  max-width: 100%;
  height: auto;
}
</style>

Somehow, the following also works. No empty line between the “img” element and “p” element. Don’t know why.

<style>
img {
  max-width: 100%;
  height: auto;
}
p:has(img:nth-child(2)) {
  display: grid; 
  grid-auto-columns: 1fr;
  grid-auto-flow: column;
  grid-gap: 0.2rem;
}
</style>

If I don’t use inline style element, use the following in a css file. Set it in the Markdown CSS setting. The “img” element is before “p” element also works. Don’t know why.

img {
  max-width: 100%;
  height: auto;
}

p:has(img:nth-child(2)) {
  display: grid; 
  grid-auto-columns: 1fr;
  grid-auto-flow: column;
  grid-gap: 0.2rem;
}

Just some of my observation after a few rounds of testing.

For now, I will just put the “img” element after “p” element. Put it in a css file. Set it in the Markdown style sheet setting for both DT and DTTG. I am happy that it works for both DT and DTTG.

Let me try to dissect that.

First, your empty line in the inline style: That’s not a CSS issue itself, it’s caused by the MD renderer garbling HTML elements when they contain an empty line. Basically, the renderer puts part of the rules in a p element, resulting in invalid style definitions. It has happened often enough to me because I tend to insert empty lines for better readability – just don’t. This is only a problem for inline style elements, not for external CSS, since the MD renderer doesn’t see that.

Next, the sequence of img and p rules: That is irrelevant. The rules are independent of one another, since one styles img and the other one p elements. So, there’s no question of specificity at all.

Again: p:has(img:nth-child(2)) does not target an img element, but a p!

And please note that in my previous post, I used a nested rule (take note of the braces!):

p:has(img:nth-child(2)) {
  display: grid;
…
  img {
    width: 100%;
  }
}

Here, the img selector is only applied to images which are child elements of a p having at least two img as child elements. That’s different from your styling, where you apply the width to all images, regardless of their position in the DOM.

In the current situation, there’s no difference to your approach, since you want all images to have the same width as their parent. But I’ve been bitten often enough by too general selectors that I prefer more specific ones.

Good. Just to clarify: It would be a bug if it didn’t. Both DT and DTTG use the (mostly) identical WebKit (yeah, I know, there are differences), which entails the (mostly) identical rendering engine. And CSS is very well standardized. We are not in the old IE days, where stuff worked in one browser but not in the other. Or rather: The current IE is called Chrome because Google is adding features left and right to it that are not yet standardized. But as long as we stick to the things caniuse.com tells us are widely implemented, there shouldn’t be a problem. Amen.

3 Likes

Thanks for the clear explanation. It all makes sense now. I will take extra care not to have empty line with the inline style: