I suspect the problem is in the processing of your style by the Markdown processor. It is finicky and unless you whitespace your content right, you can end up with paragraph tags that break it. I think often you need spacing between the style tags and the styling content.
Let’s do a quick worked example starting with some content in a draft to process just in case you’ve missed something else on the way.
Here I have created a bit of Markdown and within it I have placed some lines of CSV text that I would like to transform into a table when previewed.
# CSV Example
"Fruit", "Colour", "Units"
"Apple", "Red", 6
"Banana", "Yellow", 9
"Passion Fruit", "Purple", 2
"Apple", "Green", 7
"Orange", "Orange", 6
*Table above correct as of 2025-09-20.*
But, I want to style the table, so let’s create the same preview file as you, iCloud Drive/Drafts/Library/Templates/PreviewStyle.txt.
<style>
table
{
border-collapse: collapse;
margin: 25px 0;
font-size: 0.9em;
font-family: sans-serif;
min-width: 400px;
box-shadow: 0 0 20px rgba(0, 0, 0, 0.15);
}
thead tr
{
background-color: #009879;
color: #ffffff;
text-align: left;
}
th, td
{
padding: 12px 15px;
}
tbody tr
{
border-bottom: 1px solid #dddddd;
}
tbody tr:nth-of-type(even)
{
background-color: #f3f3f3;
}
tbody tr:last-of-type
{
border-bottom: 2px solid #009879;
}
tbody tr.active-row
{
font-weight: bold;
color: #009879;
}
</style>
I can then create an action with a script step that:
- Takes the content of the current draft.
- Converts the CSV lines to a Markdown table.
- Adds the styling from file.
- Converts that all to HTML and shows it as a preview.
// Initialise content
const STYLEDCONTENT = draft.processTemplate("\n\n[[template|PreviewStyle.txt]]\n\n") + convertCsvBlocksToMarkdown(draft.content);
// Initialise build
let hp = HTMLPreview.create();
let mmd = MultiMarkdown.create();
mmd.format = "html";
// Generate preview
hp.show(mmd.render(STYLEDCONTENT));
For anyone who wants the extra code to process the draft content’s CSV into a Markdown table format, it is here, but isn’t actually all that relevant to the underlying request.
function convertCsvBlocksToMarkdown(p_strMD)
{
// Initialise
let astrLines = p_strMD.split(/\r?\n/);
let astrOut = [];
let inCode = false;
let i = 0;
// Process lines
while (i < astrLines.length)
{
const LINE = astrLines[i];
// Toggle state for any fenced codeblock
if (LINE.trim().startsWith('```'))
{
inCode = !inCode;
astrOut.push(LINE);
i++;
continue;
}
// If we're not in a code block and this line looks like CSV (contains a comma),
// gather subsequent consecutive lines that also look like CSV lines.
if (!inCode && LINE.trim() !== '' && LINE.indexOf(',') !== -1)
{
const BLOCK = [];
while (i < astrLines.length)
{
let strCurrentLine = astrLines[i];
// Blank line ends consecutive block of lines
if (strCurrentLine.trim() === '') break;
// No comma, so not CSV-like
if (strCurrentLine.indexOf(',') === -1) break;
// Don't cross code fences
if (strCurrentLine.trim().startsWith('```')) break;
BLOCK.push(strCurrentLine);
i++;
}
// Only treat as CSV if there's at least a header + one data row (two CSV lines)
if (BLOCK.length >= 2)
{
const ROWS = BLOCK.map(parseCSVLine);
const COLUMNCOUNT = Math.max(...ROWS.map(r => r.length), ROWS[0].length);
// Normalise rows to same column count
for (const r of ROWS) while (r.length < COLUMNCOUNT) r.push('');
// Build markdown table
astrOut.push('| ' + ROWS[0].map(escapeCell).join(' | ') + ' |');
astrOut.push('| ' + new Array(COLUMNCOUNT).fill('---').join(' | ') + ' |');
for (let r = 1; r < ROWS.length; r++) astrOut.push('| ' + ROWS[r].map(escapeCell).join(' | ') + ' |');
}
else astrOut.push(...BLOCK);
continue;
}
// default: copy the line
astrOut.push(LINE);
i++;
}
return astrOut.join('\n');
}
function parseCSVLine(p_strLine)
{
const cells = [];
let j = 0;
let L = p_strLine.length;
while (j < L)
{
// Skip leading spaces
while (j < L && /\s/.test(p_strLine[j])) j++;
if (j >= L) { cells.push(''); break; }
let cell = '';
if (p_strLine[j] === '"')
{
// Quoted field
// (Skip opening quote)
j++;
while (j < L)
{
if (p_strLine[j] === '"')
{
if (j + 1 < L && p_strLine[j + 1] === '"')
{
// Escaped quote
cell += '"';
j += 2;
}
else
{
// Closing quote
j++;
break;
}
}
else
{
cell += p_strLine[j++];
}
}
// Skip any whitespace after the closing quote
while (j < L && /\s/.test(p_strLine[j])) j++;
if (j < L && p_strLine[j] === ',') j++;
}
else
{
// Unquoted field
while (j < L && p_strLine[j] !== ',') {cell += p_strLine[j++];}
if (j < L && p_strLine[j] === ',') j++;
cell = cell.trim();
}
cells.push(cell);
// Handling for trailing comma that would imply an empty final cell (e.g. "a,b,")
if (j >= L && L > 0 && p_strLine[L - 1] === ',') cells.push('');
}
return cells;
}
function escapeCell(p_strInput)
{
if (p_strInput == null) return '';
return String(p_strInput).replace(/\|/g, '\\|');
}
When I run the preview, I then get the following preview that includes the styling.
When you run your own, check under tools for the Copy HTML option. You can use this to grab your HTML and see if it looks how you would expect it to.
For this example, it looks like this.
<html><head><meta charset="UTF-8"><style>
<pre><code>table
{
border-collapse: collapse;
margin: 25px 0;
font-size: 0.9em;
font-family: sans-serif;
min-width: 400px;
box-shadow: 0 0 20px rgba(0, 0, 0, 0.15);
}
thead tr
{
background-color: #009879;
color: #ffffff;
text-align: left;
}
th, td
{
padding: 12px 15px;
}
tbody tr
{
border-bottom: 1px solid #dddddd;
}
tbody tr:nth-of-type(even)
{
background-color: #f3f3f3;
}
tbody tr:last-of-type
{
border-bottom: 2px solid #009879;
}
tbody tr.active-row
{
font-weight: bold;
color: #009879;
}
</code></pre>
</style>
</head><body><h1>CSV Example</h1>
<table>
<colgroup>
<col>
<col>
<col>
</colgroup>
<thead>
<tr>
<th> Fruit </th>
<th> Colour </th>
<th> Units </th>
</tr>
</thead>
<tbody>
<tr>
<td> Apple </td>
<td> Red </td>
<td> 6 </td>
</tr>
<tr>
<td> Banana </td>
<td> Yellow </td>
<td> 9 </td>
</tr>
<tr>
<td> Passion Fruit </td>
<td> Purple </td>
<td> 2 </td>
</tr>
<tr>
<td> Apple </td>
<td> Green </td>
<td> 7 </td>
</tr>
<tr>
<td> Orange </td>
<td> Orange </td>
<td> 6 </td>
</tr>
</tbody>
</table>
<p><em>Table above correct as of 2025–09–20.</em></p></body></html>
Hopefully, that gives you enough step-by-step to figure out what is going on, but as I noted. I wouldn’t be surprised if you were getting extra paragraph tags in your style section.