[Pitch] Community-maintained HTML and CSS Swift types

[Pitch] Community-maintained HTML and CSS Swift types

The Problem

Every Swift web framework reinvents HTML and CSS types:

  • Plot, Elementary, and coenttb/swift-html have their own element types
  • Vapor's Leaf and pointfreeco/swift-html primarily use strings
  • HTMLKit uses macros

Each new library starts from scratch, and this fragmentation means:

  • No shared tooling between frameworks
  • Repeated work modeling the same specs
  • Incompatible type definitions
  • Lost opportunity for ecosystem growth

Proposed Solution

What if we had community-maintained packages that provide:

  • Complete, spec-compliant HTML element and attribute types
  • Complete CSS property and value types
  • Just the types—no opinions on rendering or DSL design

Think of it like swift-http-types but for HTML/CSS.

Questions for the Community

  1. Is there interest in shared HTML/CSS type definitions?
  2. What would you need from such packages?
  3. Would you consider adopting them in your libraries?
  4. Should this live under a Swift community organization?

Proof of Concept

A primary goal of the new coenttb/swift-html is to use domain-accurate Swift value types to create HTML elements, add HTML attributes and style with CSS.

This required HTML and CSS domain models, for which I've created initial implementations:

These are working implementations, but I'm happy to discuss to transfer ownership, redesign APIs, or merge with existing efforts. The goal is community consensus, not personal ownership.

12 Likes

Author of HTMLKit here. What you're proposing sounds amazing, and is on my 1.0.0 roadmap to improve the development and abstraction of my library.


Obviously, I would instantly use fully compliant, documented HTML and CSS types in my libraries if that meant I didn't need to extensively develop them :wink:.

I would also like to see native support for custom attributes & elements (plus popular elements like apple-pay-button?), embedded, HTMX, some sort of templating, and an option to not use Foundation.

All that being said, your two implementations of each look very promising and along the lines of how I would do it. I look forward to use them one day. Great job!

3 Likes

Thanks for the enthusiastic feedback, Evan! It's great to hear this aligns with your HTMLKit roadmap.

I'd love to explore how the current implementations could work with HTMLKit. To give you a concrete sense of the integration approach, check out the PointFreeHTML integration layer I built: swift-html-css-pointfree. This shows how the base types can be adapted to work with existing frameworks while maintaining their specific APIs and design philosophies.

A similar html-css-htmlkit integration package could bridge the shared types with HTMLKit's architecture. This approach lets you keep HTMLKit's unique features and design decisions while benefiting from the shared type definitions underneath.

Regarding your feature wishlist (custom attributes/elements, HTMX support, Foundation-free option, etc.) - these are exactly the kinds of requirements I'd love to gather from the community. Some might belong in the core type packages, others in framework-specific integration layers. For example, custom attributes could be handled at the rendering layer - like how PointFreeHTML's .attribute method on the HTML protocol provides this flexibility.

Would you be interested in taking a look at the current implementations and sharing your thoughts on what would work well for HTMLKit's needs? Your feedback is highly appreciated.

2 Likes

Hi, author of Elementary here!

I appreciate the initiative and the enthusiasm, the overall goal does sound worthy of pursuit. However, I want to be candid and share why I remain a bit skeptical at this stage.

On the HTML types part
Maybe I just lack the fantasy, but my gut-feeling is that there will never be "general purpose" types that fit seamlessly into a "bring your own rending engine"-style setup properly. There would like be likely be compromises in DX, runtime performance, or both.

Skimming over a few thoughts:

  • what about the storage for "child" elements?
  • store all "known attributes" on elements inline? (a bit wasteful?)
  • bespoke elements for each tag type means bespoke rending implementation for each
  • extending attributes (like for htmx, data-star, ...) tricky API-wise

On the HTML attribute value types part
This part could be quite nice, no objections here. I could see using these.

On the CSS types part
This one could also be quite useful, but I think it misses the "bigger problem" about CSS in swift: How to actually use it?

If all you do is define a "static" style sheet: why bother with swift? write CSS directly

If you want inline styles?: Ok, fine, but that only gets you 50% of the way - this is not a production-worthy solution on its own.

If you want to style "components" that you define in swift using fancy HTML type: how to get that CSS out of there, possibly scoped, and assigned to the HTML tags?

If you want dynamic CSS in the WASM app or similar? Well, there is a whole lot missing around these CSS types to begin with.


To summarize, spoken candidly, coding out all these types is neither particularly difficult nor time-consuming (in this day and age, any AI will probably spit these out in a few minutes). The tricky part is figuring out which exact shape these types should have to fit the greater DX and runtime characteristics of your web framework - potentially for both SSR and CSR.

For me, the core question remains: “How can we best create web apps in Swift?” – and not (yet) “Do we have all CSS values covered?”

2 Likes

Hi Simon, thanks for the comprehensive and candid reply. Let me address your specific concerns based on my experience with swift-html-css-pointfree.

On HTML Types & Storage

You're right that perfect generalization across all rendering engines might not be achievable. The goal is to provide a solid foundation that frameworks can extend or wrap as needed.

Regarding child element storage - PointFree's solution uses callAsFunction to return types conforming to their HTML protocol:

public struct ContentDivision: Element {
    public static var tag: String { "div" }
    public init() {}
}

extension ContentDivision {
    public func callAsFunction(@HTMLBuilder _ content: () -> some HTML) -> some HTML {
        HTMLElement(tag: Self.tag) { content() }
    }
}

This enables intuitive syntax while only requiring rendering logic for HTMLElement, not each element type. It however does't limit what kinds of elements can be child elements for a particular element, only that it can have child elements (or not).

On Extending Attributes

Custom attributes (htmx, data-*, etc.) are handled through PointFreeHTML's .attribute method on the HTML protocol. This provides complete flexibility while maintaining type safety for standard attributes. Because HTMLElement conforms to the HTML protocol, each element can access the .attribute method. You can also extend any HTML element type or the HTML protocol with more type-safe custom attributes, such as for htmx.

On CSS: Beyond "50% of the way"

Static stylesheets in Swift offer real benefits: type safety, IDE autocomplete, and compile-time validation.

Ideally you write inline styles-as in SwiftUI-but generate a stylesheet. Rather than inline styles, PointFreeHTML generates optimized CSS classes. Consider the following test in swift-html-css-pointfree:

@Test("Labeled Input renders correctly in a div")
func labeledInputInDiv() {
    assertInlineSnapshot(
        of: HTMLDocument {
            div {
                label {
                    "label-text"
                    input.text
                        .color(.red)
                }
                    .color(.red)
            }
        },
        as: .html
    ) {
        """
        <!doctype html>
        <html lang="en">
          <head>
            <style>
        .color-dMYaj4{color:red}

            </style>
          </head>
          <body>
        <div><label class="color-dMYaj4">label-text<input class="color-dMYaj4" type="text"></label>
        </div>
          </body>
        </html>
        """
    }
}

Note that assertInlineSnapshot takes a type and then renders it in the closure. I.e. the lines between """...""" are generated by the HTMLDocument instantiation.

In release mode, the class name is minified. This provides a production-ready solution with proper stylesheet generation and efficient class deduplication - hopefully addressing your concern about going beyond inline styles.

On WASM and Dynamic CSS

You're absolutely right that WASM presents additional challenges. This is an area that needs more exploration, and I don't have all the answers yet.

Moving Forward

I appreciate your focus on "How can we best create web apps in Swift?" Perhaps these shared types could be one piece of that larger puzzle - providing a foundation that frameworks like Elementary can build upon in their own unique ways. However, some frameworks might be too tightly coupled to their element definitions for this to be feasible. The goal isn't to force frameworks a particular way, but to eliminate redundant work on type definitions where possible, so we can focus on the harder problems you've identified.

I have arrived at essentially the same approach for Elementary "component styling" before, but ultimately I discarded this idea. However, I am not saying this is not cool or that it won't work for certain cases.

My reasons for dropping this approach (for SSR) was:

  • ultimately, I think the stylesheet here needs to fall out at compile time
  • I believe a cacheable, standalone CSS file is a "must-have", and a dynamic in-place style section in the header is just not good enough
  • Ideally there should be (close to) zero "compute" for these styles at runtime
  • In its full generality, for dynamic apps, it would be easy to "miss" styles the the header unless the entire chain is super careful

But that's just my opinion ; ) Maybe this approach is good enough and I am just too picky.

I can almost smell a way in which macros can add the special compile-time sauce to "bundle" a swift codebase into a proper web deployment - but it did not click yet for me.

Also, I hope I don't come across as a pessimistic nay-sayer. I love that there is movement in this space!

4 Likes

You can do autocomplete/validation for CSS files. There is also SCSS/SASS to help you build them.

As someone, who regularly blocks elements or injects CSS on sites, I have a much easier time when CSS class names make sense (like "sidebar" or "ad-container") instead of using tailwind nonsense or autogenerated names.

3 Likes

Yes, I looked into the libraries a little more. I was more thinking of a drop-in replacement for the element, attribute and css types, not separate integration packages. Since HTMLKit's design is mainly syntax parsing due to the usage of macros for optimal runtime performance, I envision a few required steps (at least 2 intermediate representations to "build" the DOM/elements during macro expansion, not to mention the parsing of each type, which it already does :frowning:; maybe tedious, but it would be worth it) to fully move over to the packages you've made. I do see an immediate problem with it not natively supporting child elements though, but I do see solutions if this is a design decision.


I don't think this is true. I can think of multiple ways to fully integrate these libraries with custom designs/rendering pipelines, the obvious choice being a wrapper.

This is the idea behind HTMLKit. Every page on https://litleagues.com is built this way incorporating dynamic content (and I am working to open-source the website soon to be an example) to the point where the bottleneck is the networking layer (which is why I am also developing a macro solution to networking :smile:).

1 Like

I agree. The stylesheet should somehow define a separate resource statically, and I suppose the HTML "page" should be somehow generic over that, so that it is statically safe. That requires some serious design effort to get right :slight_smile:

Generally a hard requirement should be streamability.

1 Like

I generally like the idea (and thanks for bringing up the topic), but I have doubts about getting it into a shape that people would actually adopt. There are just too many ways in which those things can be modeled and would be used by different rendering engines and what makes sense for them.

To give some examples, let's look at just the Anchor tag structure.

  1. The first thing I noticed is that it is really big. Just an empty one takes 144 bytes w/o any actual content! The string <a/> just takes 4 bytes (16 if done as a String), even <a href="#"> would probably fit into a tagged pointer. (don't get me wrong, that may be a fine choice for a specific framework, but it wouldn't work for arbitrary ones)
  2. Naming is quite opinionated, e.g. providing specific names like typealias a = Anchor should be a thing provided by a specific framework (and is it a or A ...). Names like hreflang or attributionsrc would probably bring up a lot of discussions.
  3. Even apparently simple things like CustomStringConvertible seems opinion specific.
  4. Also it doesn't actually seem to cover element containment rules? Modeling such in a tree would also often require an in-memory transformation of the resource vs proper streaming.

It's a worthy idea. But I suspect very hard to implement.

P.S.: I'm not even sure swift-http-types is really used that much in practice (in part because it doesn't cover the content)? Is it? It is also much simpler because it really only tries to streamlined two very specific frameworks: NIO and Foundation.

1 Like

I tried implementing swift-html-types into HTMLKit with minor success. There is some key data and logic missing before it can be used. Some improvements I've thought of terms of generics, logic, memory footprint, and functionality:

(there are probably more)

list
  • removing the lowercase typealias (other libraries can do this if they wish)
  • adding support for dynamic values (currently cannot; literals are required)
  • support for optional values in more areas
  • better naming of protocols/types by prefixing some with HTML
  • use camelCase for variable names (not html styled; other libraries can do this if they wish)
  • an easy way to check if the element is a void element (something like @inlinable public static var isVoid: Bool { true })
  • having an attribute value type per Attribute
  • adding some missing attributes (ARIA, Command, CommandFor, etc)
    • Form is missing a acceptCharset in the initializer
  • rearranging stored values for minimal byte usage (obviously can't with some elements)
  • packing data when applicable (like the Button element)
  • replacing the following patterns with @inlinable public static var _: Self/String { "xyz" }
    • public static let _: Self = "xyz"
    • public static let _: String = "xyz"

I agree, wasted bytes is sub-optimal. Compile-time rendering is the only solution to avoid this without sacrificing convenience or functionality. How big of a problem is it really? What HTML libraries have you used that are better? benchmark comparing different library's throughput/rendering performance

That is a great feature suggestion!

1 Like

How big of a problem is it really ?

Well, it's a 36x overhead for something that is supposed to be a core component of any such Swift framework. There are certainly a lot of uses cases where this doesn't matter in any way, but it does not look like a good baseline to start with.

Maybe this thing should not actually provide any concrete value types, except maybe for standard defined enums, but just be a set of protocols fixing the API.
E.g.

protocol Element {
  associatedtype Content
}
protocol Anchor: Element {
  associatedtype Content: Text ... whatever, not having `||` types hurts here
  init(href: ..., allowed content)
}

But honestly no idea.

1 Like

Very happy to see such lively discussion! The detailed feedback on swift-html-types (which is indeed a working proof of concept) has been incredibly valuable.

Immediate Improvements Based on Feedback

I've already started processing several suggestions:

  • Move lowercase typealias declarations outside the core package
  • Switch from static let pattern to @inlinable static var for better performance
  • Rename Element protocol to HTMLElement for clarity
  • Add basic void element detection (though this needs refinement)
  • Begin transition to camelCase naming conventions

Addressing the Core Technical Concerns

Memory Footprint & Performance:

Helge's point about the 144-byte overhead is well-taken. The current implementation prioritizes type safety and comprehensive attribute modeling, but this clearly creates a baseline that's too heavy for many use cases. I'll need community help here.

Storage Optimization:

Evan's suggestions about memory layout and data packing are spot-on, but I'll need community help here, too. The tradeoff between convenience and efficiency isn't immediately obvious to me—would anyone be willing to contribute a sample implementation or pull request showing how to optimize specific elements?

Element Containment Rules:

Evan* ~Simon~ and others raised questions about enforcing HTML containment rules. This touches on a fundamental design question: should these types enforce semantic correctness at compile-time, or should that be left to consumer packages? I lean toward the latter for maximum flexibility, but I'd love to hear more thoughts.

Real-World Usage

For context, I'm already using swift-html-types in production for coenttb.com. The full implementation is open source at coenttb-com-server, which might help illustrate both the potential and current limitations of the approach.

I don't think I have ; ) But I do have an opinion about it: ignore it.

HTML is such a wild ride, allowing anything in anything is my solution to this problem.

Ultimately you want high-level components composed together. Passing semantic tags through the the type system for "containment rule validation" is getting unwieldy very quickly (and I have tried it ; ).

Also, there are so many different ways HTML + CSS (+ web components + JS craziness on top) are (ab)used these days, trying to fully enforce "the spec" is a non-goal (to me at least). It's called a living standard for a reason. Ain't nobody got time for that ; )

6 Likes

If we envision that these types should be used in a hypothetical swift-html-parse library, it might be a good idea to stay away from too rigid enforcement of containment rules, as there is a lot of questionable HTML out there to parse.

1 Like

Hey, I definitely think this is worth exploring. Did we consider streaming-production of HTML too? I would assume that for some pages it's quite wasteful to first provide all resources to hold a whole page in memory to then serialise and send over the network. It might be much better to produce the HTML in a stream, especially if it partially comes from disk.

I know too little about this space so I don't know if it's worthwhile the effort but I think we should definitely consciously either support or explicitly not support streaming production.

From a technical perspective, I'd love if we could produce megabytes worth of HTML without ever holding more than a few kB in memory at the same time. Especially loading the on-disk resources on-the-fly without ever paying the memory costs of having them in memory as one big blob. (FWIW, SwiftNIO, Vapor & Hummingbird are all capable of producing a streaming response just fine)

1 Like

FWIW I implemented a version of this with Elementary combined with async/await and AsyncSequence support.

I think its pretty cool actually - for your consideration ; )

1 Like

My impression is that streaming support would need to be implemented at the consumer/renderer level rather than in swift-html-types itself, but please let me know if I'm mistaken!

swift-html-css-pointfree integrates swift-html-typeswithpointfree-html as the rendering engine, and does not support streaming.

As Simon said, Elementary does have a version that supports streaming.

Both packages could rely on swift-html-types, and implement streaming outside of swift-html-types.

1 Like

FWIW I implemented a version of this with Elementary combined with async/await and AsyncSequence support.

Super cool. Did you actually try to combine it with NIOFileSystem which has full AsyncSequence-based streaming support?

2 Likes

Elementary itself is pretty bare-bones and has zero dependencies. But since you can use any AsyncSequence in your HTML types I am sure including streamed file-content will work just fine.

I have mainly used mongodb cursors as streams (MongoKitten) and it just works™ ; )