SharePoint modern view formatting with JSON – part 2 of 2
At a Glance
- Target Audience
- SharePoint Admins, Power Users, M365 Developers
- Problem Solved
- Ugly SharePoint list interfaces lacking polish, images, links, conditional display without custom code.
- Use Case
- Responsive card views for venue/event/product lists in SharePoint modern sites.
Most companies accept ugly software interfaces because they think fixing them requires custom code. It doesn't. You just need JSON and a little bit of patience.
In part 1, we built the structural skeleton. We set up the grid to hold our data. Now we need to breathe life into it. We are going to inject real list items into that skeleton so you end up with a polished interface that looks like this.
Desktop view:
Responsive mobile view:
The Secret to Referencing Column Values
Stop guessing your column names. The display name you see on the screen is a lie. SharePoint relies entirely on the internal column name. If you get this wrong, your JSON fails.
To find the true internal name, go to your list settings and click on your target column.
This takes you to the edit page.
Look at the URL in your browser. The text at the very end of that web address is your internal name. It is found directly in the URL parameter.
Notice the spaces? SharePoint hates spaces. It encodes them as x0020 to safely use them as XML attributes.
To reference a column in your JSON, you add a dollar sign and wrap the name in square brackets. It is strictly case-sensitive. This syntax is non-negotiable.
[$Title][$Maximum_x0020_capacity]
We are going to build this out in three distinct sections. Div 1 holds the image. Div 2 holds the core details like title and address. Div 3 holds the rating and price.
If you hit errors while writing this, do not panic. Missing a single comma will break the entire view. Compare your work against mine using a code difference checker. Just paste them side by side. The tool will highlight exactly where you went wrong.
Building Div 1: The Image
We start with the visual hook. Set the elmType to img. This allows us to use the src attribute to pull the picture URL directly from the list item. The style properties simply keep the image contained within the div.
{
"elmType": "img",
"style": {
"width": "260px",
"height": "160px"
},
"attributes": {
"src": "[$Picture]"
}
}
Since the image element is a child of Div 1, we must place it inside a child object. Here is the complete code for the first block.
{
"elmType": "div",
"__comment": "Div 1",
"style": {
"flex-grow": "1",
"display": "flex",
"flex-direction": "column",
"flex-wrap": "nowrap",
"align-items": "stretch",
"max-width": "260px"
},
"children": [
{
"elmType": "img",
"style": {
"width": "260px",
"height": "160px"
},
"attributes": {
"src": "[$Picture]"
}
}
]
}
Paste this JSON into your format view section. You will see the image load.
Building Div 2: The Core Details
Create a child object under Div 2. We will stack the title, location, and capacity vertically.
For the title, we use a span. It is the perfect element for holding text. We want a clean interface. We will use Office-UI fabric classes to keep the typography consistent.
We also use an if statement for the display property. If the field is empty in SharePoint, we hide the block entirely. Empty spaces ruin good design.
{
"elmType": "span",
"txtContent": "=[$Title]",
"style": {
"display": "=if([$Title] == '', 'none', 'block')"
},
"attributes": {
"class": "ms-fontSize-l ms-fontWeight-semibold ms-fontColor-neutralPrimary"
}
}
Next is the Location label. This is static text to guide the user. No if statement is needed because this label never changes.
{
"elmType": "span",
"txtContent": "Location",
"style": {
"display": "block",
"padding-top": "20px"
},
"attributes": {
"class": "ms-fontSize-l ms-fontWeight-semibold ms-fontColor-neutralPrimary"
}
}
Then we pull the actual address value.
{
"elmType": "span",
"txtContent": "[$Address]",
"style": {
"display": "=if([$Address] == '', 'none', 'block')"
}
}
Now for the map icon. We use an a tag to build a clickable link. We combine the title and address to generate a dynamic Google Maps search. Why make users manually copy and paste an address when you can automate it?
You can grab any icon from the official Fabric catalog here: https://developer.microsoft.com/en-us/fabric#/styles/icons.
{
"elmType": "a",
"attributes": {
"iconName": "MapPin",
"class": "ms-fontColor-themePrimary",
"target": "_blank",
"href": "='http://maps.google.com/?q=' + ( [$Title] + ' ') + ( [$Address])"
},
"style": {
"font-size": "15px",
"padding-top": "5px",
"cursor": "pointer"
}
}
Finally, the capacity. We prepend the word "Capacity" to the raw number for better context.
{
"elmType": "span",
"txtContent": "='Capacity ' + [$Maximum_x0020_capacity]",
"style": {
"display": "=if([$Maximum_x0020_capacity] == '', 'none', 'block')",
"padding-top": "20px"
}
}
Wrap all of these elements inside the Div 2 child object.
Building Div 3: Ratings and Actions
We need the rating number and a star icon sitting side by side. We achieve this by nesting a smaller div inside Div 3.
First, build the container.
{
"elmType": "div",
"_comment_": "RATING & STAR DIV",
"style": {
"display": "block",
"font-size": "15px",
"font-weight": "bold"
}
}
Then add the rating number and the star icon as children.
{
"elmType": "span",
"style": {
"padding-right": "5px",
"display": "=if([$Rating] == '', 'none', 'block')"
}
}
{
"elmType": "span",
"attributes": {
"iconName": "FavoriteStarFill",
"class": "ms-fontColor-themePrimary"
}
}
Close the child object immediately after these two elements. If you forget that bracket, everything below it gets trapped inside the rating div.
Next, we add the Price label and the actual price value. We prepend a currency symbol just like we did with capacity. Think about how the user perceives the data. Raw numbers are confusing. Formatted numbers are clear.
{
"elmType": "span",
"txtContent": "Price",
"style": {
"display": "block",
"padding-top": "20px"
},
"attributes": {
"class": "ms-fontSize-l ms-fontWeight-semibold ms-fontColor-neutralPrimary"
}
}
{
"elmType": "span",
"txtContent": "='£' +[$Price]",
"style": {
"display": "=if([$price] == '', 'none', 'block')"
}
}
Finally, the action button. True buttons have strict limitations in modern view formatting. External URLs are one of those limitations. So we use an anchor tag styled to look clean. We set the target to _blank so it opens in a new tab.
{
"elmType": "a",
"txtContent": "View Venue Details",
"style": {
"border": "none",
"padding-top": "32px",
"background-color": "transparent",
"color": "#0078d7",
"padding-left": "0px",
"text-align": "left",
"cursor": "pointer"
},
"attributes": {
"target": "_blank",
"href": "[$Venue_x0020_link]"
}
}
The Final Assembly
That is all the formatting complete. When you piece it together, your JSON will look like this.
{
"$schema": "https://developer.microsoft.com/json-schemas/sp/view-formatting.schema.json",
"hideSelection": true,
"hideColumnHeader": true,
"rowFormatter": {
"elmType": "div",
"_comment_": "MAIN DIV",
"attributes": {
"class": "ms-bgColor-neutralLighter"
},
"style": {
"display": "flex",
"flex-wrap": "wrap",
"align-items": "stretch",
"flex-direction": "row",
"padding": "20px",
"margin-bottom": "16px",
"max-width": "850px",
"border-radius": "8px"
},
"children": [
{
"elmType": "div",
"__comment": "DIV 1",
"style": {
"flex-grow": "1",
"display": "flex",
"flex-direction": "column",
"flex-wrap": "nowrap",
"align-items": "stretch",
"max-width": "260px"
},
"children": [
{
"elmType": "img",
"style": {
"width": "260px",
"height": "160px"
},
"attributes": {
"src": "[$Picture]"
}
}
]
},
{
"elmType": "div",
"__comment": "DIV 2",
"style": {
"flex-grow": "1",
"display": "flex",
"flex-direction": "column",
"flex-wrap": "nowrap",
"align-items": "center",
"max-width": "360px",
"min-width": "205px"
},
"children": [
{
"elmType": "span",
"txtContent": "=[$Title]",
"style": {
"display": "block"
},
"attributes": {
"class": "ms-fontSize-l ms-fontWeight-semibold ms-fontColor-neutralPrimary"
}
},
{
"elmType": "span",
"txtContent": "Location",
"style": {
"display": "block",
"padding-top": "20px"
},
"attributes": {
"class": "ms-fontSize-l ms-fontWeight-semibold ms-fontColor-neutralPrimary"
}
},
{
"elmType": "span",
"txtContent": "[$Address]"
},
{
"elmType": "a",
"attributes": {
"iconName": "MapPin",
"class": "ms-fontColor-themePrimary",
"target": "_blank",
"href": "='http://maps.google.com/?q=' + ( [$Title] + ' ') + ( [$Address])"
},
"style": {
"font-size": "15px",
"padding-top": "5px",
"cursor": "pointer"
}
},
{
"elmType": "span",
"txtContent": "='Capacity ' + [$Maximum_x0020_capacity]",
"style": {
"display": "block",
"padding-top": "20px"
}
}
]
},
{
"elmType": "div",
"__comment": "DIV 3",
"style": {
"flex-grow": "1",
"display": "flex",
"flex-direction": "column",
"align-items": "center",
"margin-top": "10px",
"max-width": "310px",
"min-width": "155px"
},
"children": [
{
"elmType": "div",
"__comment": "RATING & STAR DIV",
"style": {
"display": "block",
"font-size": "15px",
"font-weight": "bold"
},
"children": [
{
"elmType": "span",
"txtContent": "[$Rating]",
"style": {
"padding-right": "5px"
}
},
{
"elmType": "span",
"attributes": {
"iconName": "FavoriteStarFill",
"class": "ms-fontColor-themePrimary"
}
}
]
},
{
"elmType": "span",
"txtContent": "Price",
"style": {
"display": "block",
"padding-top": "20px"
},
"attributes": {
"class": "ms-fontSize-l ms-fontWeight-semibold ms-fontColor-neutralPrimary"
}
},
{
"elmType": "span",
"txtContent": "='£' +[$Price]",
"style": {
"display": "block"
}
},
{
"elmType": "a",
"txtContent": "View Venue Details",
"style": {
"border": "none",
"padding-top": "32px",
"background-color": "transparent",
"color": "#0078d7",
"padding-left": "0px",
"text-align": "left",
"cursor": "pointer"
},
"attributes": {
"target": "_blank",
"href": "[$Venue_x0020_link]"
}
}
]
}
]
}
}
You can grab the complete source code from my repository right here: https://github.com/Jamie-Bray/JSON-view-tutorial.
We took a boring list and made it functional. We pulled in column values. We handled images. We built responsive layouts. We dynamically generated map links. I built this to cover almost every scenario you will face when formatting a view. You can rip pieces out of this code and apply them to your own projects.
You don't need a massive development budget to build great tools. You just need to do the boring work when you don't feel like it.
If you want to dig deeper into the official documentation, review these Microsoft resources:
https://docs.microsoft.com/en-us/sharepoint/dev/declarative-customization/view-formatting
https://github.com/SharePoint/sp-dev-docs/blob/master/docs/declarative-customization/view-formatting.md

