Better In-File Media Handling

I understand that “One Feature per Feature Request,” but I think that in this case, it cannot be split into different Feature Requests – the integrity of the idea will simply be lost.
I have separated what could be separated into a separate Feature Request as the most independent feature, but everything else, in my opinion, should remain a single whole, a single idea for improving media handling.
First and foremost, this Feature Request can be considered to be about the problem of handling media in files, and the solutions I propose are just some of the possible solutions, and it is likely that this can be solved in other ways.
I apologize if this causes any inconvenience or confusion.

Use case or problem

When taking notes, handling media files seems very inconvenient:

  • In most cases, images are placed one below the other by default and take up too much space, spoiling the appearance of the note and making it rather inconvenient to view and interact with – you have to scroll through all the inserted media files. This layout is simply inconvenient for viewing.
  • Moving images, like any other blocks in Obsidian, is also inconvenient – there is a noticeable lack of Drag & Drop for the graphic “handle”.
  • Adjusting the size of images is too inconvenient, requires unnecessary actions, and it is easier not to do it at all and put up with the inconveniences caused by large media files inserted into the note than to do it every time. And even if you reduce all images using CSS, some of them will simply be too small.
  • When you need to view an image in more detail, you have to open it in a third-party image viewer and view it there.

* Media = Images, GIFs, Videos

Proposed solution

I see the following options for solving these problems:

Layout:

Open and Pan & Zoom:

  • Add the option to open an media on top of a note by clicking on it with the left mouse button, and to pan and zoom on the opened media .
    (The captured GIF won’t upload.)

Co-Features

Drag & Drop and Resize:

  • Add the option to drag media to a new location using the graphics “handle”, including forming the Grid Layout mentioned in the first point.
    (The captured GIF won’t upload.)
  • Add the option to change the media size by interacting with the corner of the image.
    (The captured GIF won’t upload.)

Captions:

  • Add the option to add captions to media

GIF Playback Control:

  • Add the option to control GIFs playback

Adapt Images to Dark Theme:

  • Add the option to adapt images to a dark theme as desired by clicking the associated button in the upper right corner.

…ultimately, users will be able to place media files in their notes in a more convenient way, interact with them more comfortably, and without the need for third-party plugins and software.
Media files are a fairly basic aspect of notes, and I have long felt that there is a serious lack of comfortable ways to work with them.

Current workaround

  • Grid Layout – CSS
  • Open and Pan & Zoom – Image Toolkit Plugin / CSS
  • Captions – CSS
  • Adapt Images to Dark Theme – CSS

… but each of these workarounds is personally inconvenient.

Related feature requests

… the rest are listed in Co-Features.


Aliases for search:
Media, Attachment, Image, Video, GIF, Handling, Interacting, Management, Carousel, Grid, Layout

1 Like

Image captions (maybe supported in GitHub/GitLab, I’m not sure):

<figure>  
  <img src ="image relative path">
  <figcaption>Image caption</figcaption>  
</figure>

 

Grid view:

Cards view can utilize image property with this.file although I don’t know which bases query can be used to query current embeds.

 

Add the option to drag media to a new location using the graphics “handle”, including forming the Grid Layout mentioned in the first point.

This can be achieved using tables although left clicking images inside table cells is inconsistent compared to normal editing view.

 

Add the option to open an media on top of a note by clicking on it with the left mouse button, and to pan and zoom on the opened media

Left clicking images already has a function: it selects the image for deletion or move with ⌘X or Ctrl + X. Users that want to customize the left click action would then need to turn into using community plugins.

 

Add the option to change the media size by interacting with the corner of the image.

This would require left clicking first. And left clicking already has a function. For example in Word you have to left click first to resize images. To some users left clicking to select image text is very useful and Obsidian doesn’t have proper incentive to change this old behavior to make old users feel bad.

Are you really suggesting doing this for every image?

Can ≠ Convenient and produce the desired result. It’s a terrible workaround.

There is probably nothing stopping from changing the behavior and making its use optional. Those who don’t want to use it won’t. Those who want to use it will. Just like most other Obsidian features.

Sure. For example bases can be turned off. Tables were quite big improvement / new feature and this was shipped to all users. Some Vim users weren’t happy about this. Web viewer might be useful and it is turned off by default.

 

Are you really suggesting doing this for every image?

I’m not saying you should type that manually. You insert pictures using text snippets (QuickAdd etc). Editing is obviously straightforward since the code is already there so you just click where text cursor needs to go.

 

Can ≠ Convenient and produce the desired result. It’s a terrible workaround.

  • lots of customization for example to filter, limit or sort file embeds
  • multiple custom views
  • can be included to only selected notes
  • different configurations can be reused with other notes

I don’t have the bases code so currently I don’t know if you can view note embeds using bases.

Why not just click on the “Add Caption” button, enter the desired text, and just continue writing, instead of looking for third-party solutions, interrupting myself to read their documentation, configuring them to suit my needs, exposing my data to risk by using third-party plugins, and cluttering my notes with excessive HTML abracadabra? Why all these perversions and inconveniences?

The problem described in this Feature Request is the inconvenience of placing media within notes (and the lack of some auxiliary functionality).
There is no need for filters, custom views, sorting, or any of that.
The number of images that will be inserted one after another will rarely exceed 2-3, so why would these 2-3 images need filters, custom views, sorting, and everything else?
What you are talking about is not suitable for solving the problem described in this Feature Request.
And, as in the case of Captions, it requires unnecessary and pointless actions/setup when it is enough to simply insert images and, depending on the situation, interact with the graphical interface as little as possible.

Thank you very much for your advice. Perhaps it will help someone who decides to visit this thread.
But I am not one of those people, and I find all this terribly inconvenient…

I believe, many users are not interested to try out different themes, because “theme” sounds like messing around with useless and distracting color plays.
This is true in many cases.

However, some elaborated themes add also features to Obsidian, examples are Minimal by kepano, Blue Topaz by Whyl and many more, like Dune by Jopp, that’s me. Since this thread asks about image placement and organization, have a look at Dune’s Picture features

I tested image grids in Dune and the experience is quite confusing when left clicking images in image grids. I would prefer syntax where you can use new lines between images i.e. tell explicitly when to start and end image grids. It seems that external images i.e.

![Obsidian](https://obsidian.md/images/banner.png)![Obsidian](https://obsidian.md/images/banner.png)![Obsidian](https://obsidian.md/images/banner.png)

are not supported in image grids.

 

In addition I tested these and didn’t work at all:

  • ![[Pict.png#cap|capture]]
  • table-c
  • table-eq
  • table-wide

SYSTEM INFO:
Obsidian version: v1.9.14
Installer version: v1.8.9
Operating system: Darwin Kernel Version 24.5.0: Tue Apr 22 19:54:26 PDT 2025; root:xnu-11417.121.6~2/RELEASE_ARM64_T8112 24.5.0

I also tested Image Grids, and…

Image Grids are implemented using Cssclass, which means that the effect that makes Image Grids work applies equally to all images within a single file. In real-world conditions, however, some images need to be placed individually, others in pairs, and others in groups of three. In other words, the effect does not need to be applied equally to all images, but it is applied nonetheless. Unfortunately, it still lacks the flexibility that Notion has. It may be a necessary workaround, but for me personally, it is not a replacement for the full functionality of placing images in Grid format.

Perhaps I am incorrect in some respects. I did not want to install the entire theme, so I simply took the piece of code responsible for Image Grids and applied it as a CSS Snippet.
I generally don’t use the Cssclass function, and I don’t want to clutter up my file metadata with it, so if I use CSS, I use universal CSS that works as desired throughout the entire vault, without having to apply situational CSS to each file individually.

In any case, thank you for what is available. It is probably difficult to achieve anything more with CSS.


I don’t know how useful this will be, but ChatGPT recently gave me this code for Image Grids.

In general, the images themselves are placed correctly—the size of the images changes dynamically depending on their amount.

But the problem is that for some reason I can’t click on any line, on any block after the images. The line simply doesn’t become active, as if I’m not clicking at all.
For me, this CSS snippet is too massive and complex, so instead of trying to understand and correct it, I decided to just accept the lack of convenient media file handling and live with the inconvenience.

/* Image Grid for Obsidian — final attempt
   - Keeps text untouched
   - Works when images are direct .internal-embed siblings OR when each image is in its own <p>/.cm-line
   - Equal-width columns for 2, 3, 4 images (override order: 4 > 3 > 2)
   - Requires :has() support (recent Obsidian builds)
*/

:root { --img-grid-gap:32px; }

/* -------------------------
   1) Basic image styling
   ------------------------- */
.internal-embed.image-embed img {
  display: block;
  width: 100%;
  height: auto;
  object-fit: contain;
  border-radius: 6px;
}

/* Keep internal embeds themselves neutral by default (no global layout changes) */
.internal-embed.image-embed {
  box-sizing: border-box;
  margin: 0;
  padding: 0;
}

/* -------------------------
   2) Case A — images are direct siblings:
   Make direct .internal-embed siblings inline-level, then set widths
   ------------------------- */

.internal-embed.image-embed {
  display: inline-block !important;
  vertical-align: top;
  max-width: 100%;
}

/* 2 images in a row (applies to both items in the pair) */
.internal-embed.image-embed:has(+ .internal-embed.image-embed),
.internal-embed.image-embed + .internal-embed.image-embed {
  width: calc((100% - var(--img-grid-gap)) / 2) !important;
}

/* 3+ images override (3 in a row) */
.internal-embed.image-embed:has(+ .internal-embed.image-embed + .internal-embed.image-embed),
.internal-embed.image-embed + .internal-embed.image-embed:has(+ .internal-embed.image-embed),
.internal-embed.image-embed + .internal-embed.image-embed + .internal-embed.image-embed {
  width: calc((100% - (2 * var(--img-grid-gap))) / 3) !important;
}

/* 4+ images override (4 in a row) */
.internal-embed.image-embed:has(+ .internal-embed.image-embed + .internal-embed.image-embed + .internal-embed.image-embed),
.internal-embed.image-embed + .internal-embed.image-embed:has(+ .internal-embed.image-embed + .internal-embed.image-embed),
.internal-embed.image-embed + .internal-embed.image-embed + .internal-embed.image-embed:has(+ .internal-embed.image-embed),
.internal-embed.image-embed + .internal-embed.image-embed + .internal-embed.image-embed + .internal-embed.image-embed {
  width: calc((100% - (3 * var(--img-grid-gap))) / 4) !important;
}

/* Add horizontal gap visual (works well with inline-block widths above) */
.internal-embed.image-embed { margin-right: var(--img-grid-gap); }
.internal-embed.image-embed:last-child { margin-right: 0; }

/* -------------------------
   3) Case B — images each inside their own paragraph (or .cm-line)
   We target paragraphs / cm-line blocks that contain ONLY an image embed,
   convert those paragraph blocks to inline blocks so adjacent paragraphs can sit horizontally,
   and then size them with the same rule-set as above.
   ------------------------- */

/* Selector for "image-only" paragraph (reading view) */
.markdown-preview-view p:has(> .internal-embed.image-embed):not(:has(> :not(.internal-embed.image-embed))) {
  display: inline-block;
  vertical-align: top;
  margin: 0;
  padding: 0;
  box-sizing: border-box;
}

/* Selector for "image-only" cm-line (live preview / source view) */
.markdown-source-view.is-live-preview .cm-line:has(> .internal-embed.image-embed):not(:has(> :not(.internal-embed.image-embed))),
.markdown-source-view.mod-cm6 .cm-line:has(> .internal-embed.image-embed):not(:has(> :not(.internal-embed.image-embed))) {
  display: inline-block;
  vertical-align: top;
  margin: 0;
  padding: 0;
  box-sizing: border-box;
}

/* Make the paragraph/.cm-line wrappers behave like the direct-embed widths above */

/* Two adjacent image-only paragraphs -> each ~50% */
.markdown-preview-view p:has(+ p:has(> .internal-embed.image-embed):not(:has(> :not(.internal-embed.image-embed)))):has(> .internal-embed.image-embed),
.markdown-preview-view p + p:has(> .internal-embed.image-embed):not(:has(> :not(.internal-embed.image-embed))) {
  width: calc((100% - var(--img-grid-gap)) / 2);
  margin-right: var(--img-grid-gap);
}

/* Same for live-preview cm-line pairs */
.markdown-source-view.is-live-preview .cm-line:has(+ .cm-line:has(> .internal-embed.image-embed)):has(> .internal-embed.image-embed),
.markdown-source-view.is-live-preview .cm-line + .cm-line:has(> .internal-embed.image-embed),
.markdown-source-view.mod-cm6 .cm-line:has(+ .cm-line:has(> .internal-embed.image-embed)):has(> .internal-embed.image-embed),
.markdown-source-view.mod-cm6 .cm-line + .cm-line:has(> .internal-embed.image-embed) {
  width: calc((100% - var(--img-grid-gap)) / 2);
  margin-right: var(--img-grid-gap);
}

/* 3 adjacent image-only paragraphs (each ~33%) */
.markdown-preview-view p:has(+ p:has(+ p:has(> .internal-embed.image-embed))):has(> .internal-embed.image-embed),
.markdown-preview-view p + p:has(+ p:has(> .internal-embed.image-embed)),
.markdown-preview-view p + p + p:has(> .internal-embed.image-embed) {
  width: calc((100% - (2 * var(--img-grid-gap))) / 3);
  margin-right: var(--img-grid-gap);
}

/* live-preview 3-chain */
.markdown-source-view.is-live-preview .cm-line:has(+ .cm-line:has(+ .cm-line:has(> .internal-embed.image-embed))):has(> .internal-embed.image-embed),
.markdown-source-view.is-live-preview .cm-line + .cm-line:has(+ .cm-line:has(> .internal-embed.image-embed)),
.markdown-source-view.is-live-preview .cm-line + .cm-line + .cm-line:has(> .internal-embed.image-embed),
.markdown-source-view.mod-cm6 .cm-line:has(+ .cm-line:has(+ .cm-line:has(> .internal-embed.image-embed))):has(> .internal-embed.image-embed),
.markdown-source-view.mod-cm6 .cm-line + .cm-line:has(+ .cm-line:has(> .internal-embed.image-embed)),
.markdown-source-view.mod-cm6 .cm-line + .cm-line + .cm-line:has(> .internal-embed.image-embed) {
  width: calc((100% - (2 * var(--img-grid-gap))) / 3);
  margin-right: var(--img-grid-gap);
}

/* 4+ chains: you can copy the pattern above and change the math; here is 4 */
.markdown-preview-view p:has(+ p:has(+ p:has(+ p:has(> .internal-embed.image-embed)))):has(> .internal-embed.image-embed),
.markdown-preview-view p + p + p + p:has(> .internal-embed.image-embed),
.markdown-preview-view p + p + p:has(+ p:has(> .internal-embed.image-embed)) {
  width: calc((100% - (3 * var(--img-grid-gap))) / 4);
  margin-right: var(--img-grid-gap);
}
.markdown-source-view.is-live-preview .cm-line:has(+ .cm-line:has(+ .cm-line:has(+ .cm-line:has(> .internal-embed.image-embed)))):has(> .internal-embed.image-embed),
.markdown-source-view.is-live-preview .cm-line + .cm-line + .cm-line + .cm-line:has(> .internal-embed.image-embed),
.markdown-source-view.is-live-preview .cm-line + .cm-line + .cm-line:has(+ .cm-line:has(> .internal-embed.image-embed)),
.markdown-source-view.mod-cm6 .cm-line:has(+ .cm-line:has(+ .cm-line:has(+ .cm-line:has(> .internal-embed.image-embed)))):has(> .internal-embed.image-embed),
.markdown-source-view.mod-cm6 .cm-line + .cm-line + .cm-line + .cm-line:has(> .internal-embed.image-embed),
.markdown-source-view.mod-cm6 .cm-line + .cm-line + .cm-line:has(+ .cm-line:has(> .internal-embed.image-embed)) {
  width: calc((100% - (3 * var(--img-grid-gap))) / 4);
  margin-right: var(--img-grid-gap);
}

/* Remove trailing right margin on last item in a chain to avoid overflow */
.internal-embed.image-embed:last-child,
.markdown-preview-view p:has(> .internal-embed.image-embed):not(:has(+ p:has(> .internal-embed.image-embed))),
.markdown-source-view.is-live-preview .cm-line:has(> .internal-embed.image-embed):not(:has(+ .cm-line:has(> .internal-embed.image-embed))) {
  margin-right: 0;
}

/* Add horizontal spacing without shrinking images */
.internal-embed.image-embed + .internal-embed.image-embed {
  margin-left: 32px !important; /* change this to desired spacing */
}

/* Optional: also vertical spacing for wrapping images */
.internal-embed.image-embed {
  margin-bottom: 8px !important; /* space between rows if images wrap */
}


/* -------------------------
   Notes:
   - This intentionally DOES NOT change line-height, text alignment, or widths of text paragraphs.
   - If your images are all inside one paragraph already (e.g. `![[a]] ![[b]] ![[c]]`) the direct-sibling rules will apply.
   - If your images are each on their own Markdown line (each gets its own <p>/.cm-line), the "image-only paragraph" rules apply.
   - If your Obsidian's Chromium doesn't support :has(), these selectors will be ignored (no harm).
*/

I don’t understand this part. Dune doesn’t add strange things to images when you left clicking images.

Image grids are very simple:
take advantage of the horizontal space to organize images. Throw images into a note and add line breaks, wherever You want.

Usually I appreciate your comments on this forums but this time you rushed with the answer. Please, if something doesn’t work then take the time to test thoroughly before reporting.
Also, this forum isn’t the appropriate place for reports, the place for reports is on my GitHub page.

Dune adds features thanks to css, this is a limitation, not everything is technically possible in css. Since I use Dune myself in eager to correct any issues.

External images load as well, maybe your network connection was down for a moment or your network rules need some tweak.

Not true. All of these options work in preview view. I tested just now.

I removed image capture from source view to avoid misplaced capture labels when viewing images in source view.

And tables are easier to handle without applied modifications, so I kept them only for preview view.
There’s nothing random in Dune, after 3 years of constant updates and 3 rewrites, features work as intended.

Image placement depends from case to case, please read more about images in my wiki under Dune’s picture feature
Some examples:

A) if you need to place single images you can do so by adding image metadata. Eg [image.png|pos-r] to place your image on the right hand-side of your note.

B) image groups are possible if you place them into a callout. Eg. >[!pict]

The goal of image grids is to create a wall of images and not to place images individually.

I tested this very thoroughly as I usually do. I’m sorry that I didn’t realize that reading view should be used. Although I didn’t get it to work in reading view so my observations remain same. I used a sandbox vault. I need to place images without line breaks to include them in the same image grid. Image grids are formed from every image but only the ones placed in same line belong to the same image grid.

 

Not true. All of these options work in preview view . I tested just now.

I’m sorry I didn’t test reading view and yes they work in reading view.

 

There’s nothing random in Dune, after 3 years of constant updates and 3 rewrites, features work as intended.

I’m sorry of my bad wording when I said “confusing experience”. Image grids should work fine if you place images in separate lines. I didn’t get this to work and by saying it I don’t imply that I’m not acknowledging your great work. I’m excited to test the features you offer and I’m aware that eventually bug reporting should be done in the corresponding repository.

To check your questions:

  1. I created a new sandbox vault by clicking on the question mark icon next to Obsidian’s settings (gear icon).
  2. I pasted a copy of Dune into “. obsidian” by going into settings>appearance>themes and clicking on the folder icon next the popup button “default”
  3. Then I added images to my sandbox vault and placed my image grid, with various image capture texts on each image, switched to preview view and didn’t see any issues .
  4. I played with Dune’s table cssclasses: table-c, table-eq as well as table-wide: all worked as expected.

To proof what I’m saying, I can send you images via PM, bc I don’t wanna spam this thread with my stuff.
Otherwise feel free to open a ticket on my GitHub page to discuss these issues there.

Thanks for improving your question, and your constructive words.