Separating Meaning From Presentation: The Modelling Decision That Determines Reuse
You style a "Featured" heading bold and 28px right inside the content, and six months later marketing wants that same field to drive an email subject line, a push notification, and an LLM summary.
You style a "Featured" heading bold and 28px right inside the content, and six months later marketing wants that same field to drive an email subject line, a push notification, and an LLM summary. Now every downstream consumer inherits an editor's font choice, and nobody can query "which articles are featured" because "featured" lives in a rich-text blob instead of a field. This is the modelling decision that quietly determines whether your content is reusable or trapped: do you store what something means, or how it looked in one place? This article argues that separating meaning from presentation is not a purity exercise. It is the difference between content you can query, syntax-check, and pipe into channels that did not exist when you modelled it, and content you have to re-key by hand every time a new surface appears.
What does separating meaning from presentation actually mean?
Separating meaning from presentation means your content model stores semantic intent (this is a call to action, this term is a defined glossary entry, this block is a product callout) rather than the visual result of that intent (blue button, bold text, boxed sidebar). The meaning lives in structured fields and typed annotations; the presentation lives in the frontend that reads them. The test is simple: could you render the same document as a web page, a voice response, an email, and a JSON payload for an agent, without editing the content? If styling decisions are baked into the stored value, the answer is no, and every new channel becomes a re-authoring project.
The failure mode is familiar. A team ships a marketing site, and editors reach for whatever the WYSIWYG toolbar offers: inline color, manual bold, a hard-coded heading size to make something stand out. Each of those choices is a presentation decision masquerading as content. It works on the one page it was written for. It breaks the moment a second consumer, a search index, a localization pipeline, or a summarization model, tries to read the same field and finds formatting where it expected structure.
The reframe is to treat presentation as a projection of meaning, computed at read time by the consuming surface, not a property stored alongside the content. When meaning is the stored artifact, presentation becomes cheap and pluralistic. You can restyle, re-theme, or add a channel without touching a single document, because the documents never knew what they looked like in the first place.
Why does mixing presentation into content destroy reuse?
Mixing presentation into content destroys reuse because it hard-codes one consumer's assumptions into a value that many consumers have to read. The instant you store "<span style='color:red'>Sale</span>" instead of a boolean isPromoted and a category reference, you have made a promise to exactly one HTML renderer and broken it for everyone else. An email client strips the span. A voice assistant reads the markup aloud. A model ingesting the field for retrieval sees noise, not signal. The content is now coupled to a rendering context it can never leave.
The cost compounds in three predictable ways. First, querying dies: you cannot filter, sort, or aggregate on intent that is buried in a formatting blob, so "show me every expired promotion" becomes a manual audit. Second, migration becomes archaeology, because to move the content anywhere you first have to reverse-engineer what the styling was standing in for. Third, governance evaporates, since a rule like "legal disclaimers must appear on every product page" is unenforceable when a disclaimer is a paragraph an editor happened to bold rather than a typed, required field the system knows about.
The counter-intuitive part is that presentation-in-content feels faster in the first week and is catastrophically slower by the second quarter. The editor who inlines a style ships today. The team that inherits that field pays for it on every channel, every migration, and every audit thereafter. Reuse is not something you add later; it is a property you either preserve or forfeit at modelling time, and formatting-as-content forfeits it silently.
How do you model semantic intent instead of visual output?
You model semantic intent by naming fields for the decision the business is making, not the pixels the decision produces. Instead of a headingLarge string, you model a title plus an eyebrow plus a boolean isFeatured, and you let the frontend decide that featured titles render at 28px. Instead of inline bold inside body copy, you define a typed annotation, a mark that says "this is a defined term" or "this is a legal reference," carrying the reference id, not the font weight. The stored value describes what the thing is; the theme decides how it looks.
Structured rich text is where this most often falls apart, because rich text is where editors expect a toolbar and toolbars invite formatting. Portable Text solves that by representing rich content as an array of typed blocks with explicit marks and annotations rather than an HTML string. A link is not <a href>, it is an annotation object with a href and, if you want, a reference to a linked document, so the same content renders as an anchor on web, a footnote in print, and a resolvable reference for an agent. You can put custom block types, a callout, a product embed, an image with alt text and layout intent, into the same array, each one structured data your renderers switch on.
The practical rule is to push every reusable decision up into a named field and leave the body for prose. If a piece of formatting carries meaning, promote it to structure: a boolean, a reference, a typed annotation, an enum. If it carries only appearance, it does not belong in the content at all; it belongs in the design system that reads the content. Model the noun and the intent, never the CSS.
Does a fixed editor force presentation back into your content?
Yes, and this is the trap most headless platforms spring without warning. A CMS can have a beautifully structured API and still push presentation into content through the back door of a rigid editing experience. If the editor only offers a generic rich-text box and a fixed set of field types, editors will smuggle meaning into formatting because the tool gave them nowhere else to put it. The data model's cleanliness is downstream of whether the editing surface let editors express intent as structure in the first place.
This is where an editor you can shape in code changes the modelling outcome rather than just the authoring comfort. Sanity Studio is a React application you configure and extend, so the input an editor sees can match the semantic field exactly: a custom input component for a promotion picker, a Structure Builder tree that groups documents by meaning rather than by folder, a portable text editor with only the annotations your model allows and none of the freeform styling it does not. When the editor can render "pick a defined term" as a real control, the editor has no reason to fake it with bold text, and the stored content stays semantic. That is the Content Operating System stance in practice: the tool adapts to how your business models content, instead of forcing your content to fit a fixed toolbar.
Fixed-editor platforms are not incapable of clean models; disciplined teams build clean models on Contentful, Storyblok, and Strapi every day. But they are fighting the tool's defaults, and every gap between what the model needs and what the editor offers is an invitation for presentation to leak back in. A customizable editor closes those gaps instead of documenting them in a style guide nobody reads.
How does clean separation pay off when a new channel appears?
Clean separation pays off the first time a channel arrives that you did not design for, which in 2026 is roughly quarterly. When content is stored as meaning, a new surface is a new projection: you write a renderer that reads the same structured fields and maps them to the new medium's presentation rules. Nothing about the content changes. When content is stored as HTML-flavored formatting, a new channel is a migration, because you have to strip one medium's presentation out of every value before the next medium can use it.
The query layer is what makes this cheap in practice. Because the fields carry intent, you can ask for exactly the shape a given channel needs in one round trip. A GROQ projection lets you request the eyebrow, title, and first two body blocks for a card layout, or the full Portable Text array plus resolved reference titles for an article page, or a stripped plain-text version for a summarization job, all from the same documents, all without the frontend knowing anything about how the content was authored. The consumer names the shape it wants; the store returns exactly that, references dereferenced, filters applied, in a single request.
The governance dividend lands here too. Because meaning is typed, rules travel with the content: a required legal reference is enforced at the schema level, a scheduled promotion has a real date field a Content Release can act on, and an audit can answer "which documents are missing a disclaimer" as a query rather than a manual review. Presentation-coupled content cannot make any of those promises, because the system does not know what the formatting was supposed to mean. Separation is not tidiness. It is the precondition for every automation, every channel, and every control you will want later.
When is it acceptable to store presentation in content?
It is acceptable to store presentation in content when the presentation is itself the meaning, and pretending otherwise is over-engineering. An editorial team that genuinely needs to pull-quote a specific sentence, float an image left for a deliberate rhetorical effect, or choose between two legitimate callout styles is making an editorial decision, not a styling accident. The honest move is to model that decision as a constrained choice, an enum of allowed layout intents, not a freeform style attribute, so the content still says "emphasize this" and the theme still owns exactly how emphasis looks in each medium.
The line to hold is between intent and implementation. "Layout: full-bleed" is an intent a print renderer and a web renderer can both interpret. "margin-top: 24px" is an implementation that means nothing off the one page it targets. Storing the former as a typed field is legitimate and reusable; storing the latter is the coupling this whole article warns against. When editors need expressive control, give them a vocabulary of semantic options, model each option explicitly, and let each renderer decide what that option looks like on its surface. Portable Text custom block types are built for exactly this: a callout block with a tone enum, an image block with an alt text field and a layout intent, structured data that carries the editorial decision without hard-coding a single pixel.
The pragmatic default is to start strict and relax deliberately. Model meaning first, and only add a presentation-flavored field when a real consumer has a real need that structure cannot express, and even then express it as a constrained, named choice. The goal was never zero presentation in the model. It was zero accidental presentation, and zero presentation that only one channel can read.
How platforms keep meaning and presentation separate
| Feature | Sanity | Contentful | Storyblok | Strapi |
|---|---|---|---|---|
| Rich text storage format | Portable Text: an array of typed blocks with explicit marks and annotations, not an HTML string, so links carry references and custom blocks carry structured data. | Rich Text stored as a structured JSON document tree, portable and renderable per channel, though the model is fixed rather than defined by your schema. | Rich text serializable to HTML or a structured object; the schema block field is flexible, with formatting options driven by the fixed editor toolbar. | Rich text field stores Markdown or a blocks JSON structure depending on config; blocks are structured, Markdown carries some presentation inline. |
| Encoding intent as typed annotations | Custom marks and annotations carry meaning (defined term, legal reference, linked document id), so the frontend decides appearance from the intent stored. | Marks and hyperlinks are structured in the Rich Text tree; custom mark types beyond the built-in set are limited by the fixed schema. | Custom link and text styles available, but semantic annotation types beyond the toolbar's offering require workarounds rather than first-class marks. | Blocks editor supports standard marks; extending with custom semantic annotation types means custom field plugins and manual rendering. |
| Editor shaped to the semantic model | Sanity Studio is a React app you extend with custom input components and Structure Builder, so an editor picks intent as a real control instead of faking it with formatting. | App framework and custom UI extensions available, though the core editing fields are fixed; deep custom inputs need app-level development. | Visual editor with some field customization; the authoring surface is largely fixed, so novel semantic controls are constrained by the platform. | Open source and customizable via plugins; custom field components are possible with React, but require building and maintaining them yourself. |
| Querying on intent, not formatting | GROQ projects exactly the shape each channel needs in one round trip, filtering and sorting on typed fields and dereferencing references inline. | GraphQL and REST let you query typed fields and filter; intent buried in Rich Text nodes is not directly queryable, only whole-field. | REST and GraphQL Content Delivery APIs query structured fields; querying inside the rich-text object for semantic marks is not a first-class filter. | REST and GraphQL with filtering on typed fields; querying semantic intent inside a blocks or Markdown field is not directly supported. |
| Constrained presentation choices as data | Model layout intent as an enum or a Portable Text custom block with a tone field, so 'emphasize this' is stored data and each renderer owns the pixels. | Content types can define enum-like validated fields for layout intent; custom block components inside rich text are more constrained. | Bloks let editors compose nestable structured components with defined fields, a strong fit for constrained, reusable layout intent. | Components and dynamic zones model reusable structured blocks with typed fields, suitable for constrained layout intent choices. |
| Governing meaning at schema level | Required references and typed fields enforce rules (a mandatory legal reference), and Content Releases act on real date fields rather than bolded text. | Validations and required fields enforced at the content type level; scheduling and releases available on higher tiers. | Field validations and scheduled publishing supported; enforcement depends on the fixed field types the platform exposes. | Field validation and required fields via the content-type builder; scheduling and release workflows depend on self-hosted setup and plugins. |