```dataviewjs
// Get pages and sort alphabetically by filename
const pages = dv.pages("#excalidraw")
.where(p => p.file.name.includes("icon -"))
.sort(p => p.file.name);
// Apply grid styles to the container
dv.container.style.display = "grid";
dv.container.style.gridTemplateColumns = "repeat(auto-fill, minmax(160px, 1fr))";
dv.container.style.gap = "16px";
dv.container.style.padding = "12px";
// Add each image as a grid item
for (let page of pages) {
const cleanName = page.file.name.replace("icon - ", "").replace(".excalidraw", "");
// Create button container instead of div
const itemButton = dv.el("button", "", {
attr: {
type: "button",
style: `
display: flex;
flex-direction: column;
align-items: center;
padding: 16px;
border-radius: 12px;
transition: all 0.2s ease;
cursor: pointer;
background: var(--background-primary);
border: 1px solid var(--background-modifier-border);
color: inherit;
font-family: inherit;
width: 100%;
min-height: 140px;
`
}
});
// Create image container with fixed dimensions
const imageContainer = dv.el("div", "", {
container: itemButton,
parent: itemButton,
attr: {
style: `
width: 80px;
height: 80px;
display: flex;
justify-content: center;
align-items: center;
margin-bottom: 10px;
overflow: hidden;
border-radius: 8px;
background: var(--background-secondary);
`
}
});
// Use dv.paragraph to render the wikilink properly with consistent sizing
dv.paragraph(`![[${page.file.name}|80]]`, {
container: imageContainer
});
// Add text label
dv.el("div", cleanName, {
parent: itemButton,
attr: {
style: `
font-size: 0.8em;
text-align: center;
color: var(--text-muted);
word-break: break-word;
line-height: 1.3;
max-width: 100%;
font-weight: 500;
`
},
container: itemButton
});
// Add enhanced hover and focus effects
itemButton.addEventListener('mouseenter', () => {
itemButton.style.transform = 'translateY(-3px) scale(1.02)';
itemButton.style.boxShadow = '0 8px 25px rgba(0,0,0,0.15)';
itemButton.style.borderColor = 'var(--interactive-accent)';
itemButton.style.background = 'var(--background-secondary)';
});
itemButton.addEventListener('mouseleave', () => {
itemButton.style.transform = 'translateY(0) scale(1)';
itemButton.style.boxShadow = 'none';
itemButton.style.borderColor = 'var(--background-modifier-border)';
itemButton.style.background = 'var(--background-primary)';
});
itemButton.addEventListener('focus', () => {
itemButton.style.outline = '2px solid var(--interactive-accent)';
itemButton.style.outlineOffset = '2px';
});
itemButton.addEventListener('blur', () => {
itemButton.style.outline = 'none';
});
// Handle click and keyboard navigation
const openFile = () => {
app.workspace.openLinkText(page.file.path, '');
};
itemButton.addEventListener('click', openFile);
itemButton.addEventListener('keydown', (e) => {
if (e.key === 'Enter' || e.key === ' ') {
e.preventDefault();
openFile();
}
});
}
Last active
August 5, 2025 17:25
-
-
Save AlbyIanna/2cc2c93386e5b508cb59e1efaf79258b to your computer and use it in GitHub Desktop.
Obsidian DataviewJS snippet for displaying Excalidraw icons in a responsive grid.
I've created this code snippet in dataviewjs (DataView plugin for Obsidian) for displaying my icon library in a nice way - built using the Excalidraw plugin by Zsolt Viczian.
Essentially, this snippet displays all #excalidraw files with names starting in "icon -" as a responsive grid of clickable image buttons. Each item shows a thumbnail preview and a label. Includes keyboard accessibility, hover/focus styles, and opens the file on click or key press. I also tested it on mobile and it works super well ✨
Here's how it looks on different screen sizes 👇
const pages = dv.pages("#excalidraw")
.where(p => p.file.name.includes("icon -"))
.sort(p => p.file.name);- Filters all notes tagged with
#excalidrawand filenames containing"icon -". - Sorts them alphabetically by filename to maintain consistent grid order.
dv.container.style.display = "grid";
dv.container.style.gridTemplateColumns = "repeat(auto-fill, minmax(160px, 1fr))";
dv.container.style.gap = "16px";
dv.container.style.padding = "12px";- Applies a responsive CSS grid to the main container.
- Ensures the layout adapts to different screen sizes while maintaining a consistent gap and padding.
Inside the for...of loop, each page becomes a styled, interactive grid item:
const itemButton = dv.el("button", "", { ... });- Acts as the main wrapper per item.
- Styled like a card with padding, border, and hover effects.
const imageContainer = dv.el("div", "", { ... });
dv.paragraph(`![[${page.file.name}|80]]`, { container: imageContainer });- Displays a thumbnail preview of the Excalidraw file using Obsidian's
![[wikilink|80]]syntax. - Keeps the image centered and cropped in a fixed-size square.
dv.el("div", cleanName, { ... });- Shows a readable label (filename without prefix/suffix).
- Styled for clarity and visual hierarchy.
itemButton.addEventListener('mouseenter', () => { ... });
itemButton.addEventListener('mouseleave', () => { ... });
itemButton.addEventListener('focus', () => { ... });
itemButton.addEventListener('blur', () => { ... });- Adds smooth transitions and shadows on hover.
- Enhances keyboard focus visibility for accessibility.
const openFile = () => {
app.workspace.openLinkText(page.file.path, '');
};
itemButton.addEventListener('click', openFile);
itemButton.addEventListener('keydown', (e) => { ... });- Opens the linked note when clicked or when pressing Enter or Space.
- Makes the grid navigable via mouse and keyboard.
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment