There is no simple way of doing this, but a possibility is to use the idea from the accepted answer for QGIS Labels with HTML formating.
You need to set the value as '' (an empty string). The size should be set to some fixed value. This interplays with how the size of the labels are defined and may need some tweaking.

(The answer I linked to used rule based labeling, in this case, the label is always the same, so one can just as well use simple labels, unless one need to use different types of labels)
I made a test layer with two fields: Header and Text. For the testing, I used '#' as the line separators, '\n' or other sensible line separators will probably work just fine. The difficult thing is then to make the multiple lines for your text. To just make a white square with the text in, I made the following expression:
'data:image/svg+xml;utf8,<svg width="600" height="300" version="1.1"
viewBox="0 0 100 50" xmlns="http://www.w3.org/2000/svg">
<g fill="#FFFFFF" fill-opacity=".7" stroke="#000000">
<rect width="100" height="50" />
</g>
<g alignment-baseline="middle" stroke-width=".25" text-anchor="middle">
<text text-anchor="start" y="15" font-size="15px">'||"Header"||'</text>'
||multilineSVGtext("Langtekst",'#',20,10,10)||'
</g>
</svg>'
To split the text into lines, I wrote the following function:
from qgis.core import *
from qgis.gui import *
@qgsfunction(args='auto', group='Custom', referenced_columns=[])
def multilineSVGtext(intext, separator, start, size, x, feature, parent):
svgfrag = ''
lines = intext.split(separator)
y=start+size1.2
for line in lines:
svgfrag += f'<text x="{x}" y="{y}" font-size="{size}" text-anchor="start">{line}</text>'
y += size1.2
return svgfrag
This gave me the following labels:

This simple labels does not work properly if there are large differences in the amount of text in the labels. To have a dynamic size, I wrote the following function:
from qgis.core import *
from qgis.gui import *
import math
@qgsfunction(args='auto', group='Custom', referenced_columns=[])
def svglabel(header,labeltext,feature,parent):
textsize = 10
start = 20
lines = labeltext.split('#') # Has used # as a line marker in the field. Other things probably works fine
height = start + textsize1.2len(lines)+textsize
length = len(max(lines, key=len))
width = math.ceil(length * 0.7 * textsize) # estimate of average width / height in a proportional font
# Width for the svg must be set to a fixed value or qgis will scale the labels differently to force the same width
svg=f'''data:image/svg+xml;utf8,<svg width="150" height="{height}" version="1.1" viewBox="0 0 150 {height}" xmlns="http://www.w3.org/2000/svg">
<g fill="#FFFFFF" fill-opacity=".7" stroke="#000000">
<rect width="{width}" height="{height}" />
</g>
<g alignment-baseline="middle" stroke-width=".25" text-anchor="start">
<text y="15" font-size="15px">{header}</text>
'''
y=start+textsize1.2
x=10
for line in lines:
svg += f'<text x="{x}" y="{y}" font-size="{textsize}">{line}</text>'
y += textsize1.2
svg += '</g></svg>'
# print(svg) # This will print the svg to the python console for debugging
return svg
this function is the called in the expression with
svglabel(headerfield,textfield)
This gives the following result with the labels set to fixed size 45mm. (The points are the orange dots behind the labels)

As can be seen, there are some problems with overlapping labels. For this case it works, but it is also here needed to play around a bit with the various labeling parameters.