` tag is used for a single sentence of text and grouping inline content.
-public final class Text: Element {
- /// Creates a new text element.
- ///
- /// Uses `` for multiple sentences, `` for one or fewer.
- ///
- /// - Parameters:
- /// - id: Unique identifier for the HTML element.
- /// - classes: An array of CSS classnames.
- /// - role: ARIA role of the element for accessibility.
- /// - label: ARIA label to describe the element.
- /// - data: Dictionary of `data-*` attributes for element relevant storing data.
- /// - content: Closure providing text content.
- public init(
- id: String? = nil,
- classes: [String]? = nil,
- role: AriaRole? = nil,
- label: String? = nil,
- data: [String: String]? = nil,
- @HTMLBuilder content: @escaping () -> [any HTML]
- ) {
- let renderedContent = content().map { $0.render() }.joined()
- let sentenceCount = renderedContent.components(
- separatedBy: CharacterSet(charactersIn: ".!?")
- )
- .filter { !$0.trimmingCharacters(in: .whitespaces).isEmpty }
- .count
- let tag = sentenceCount > 1 ? "p" : "span"
- super.init(
- tag: tag,
- id: id,
- classes: classes,
- role: role,
- label: label,
- data: data,
- content: content
- )
- }
-}
-
-/// Defines levels for HTML heading tags from h1 to h6.
-public enum HeadingLevel: String {
- /// Large title, most prominent heading (h1).
- case largeTitle = "h1"
- /// Title, second most prominent heading (h2).
- case title = "h2"
- /// Headline, third most prominent heading (h3).
- case headline = "h3"
- /// Subheadline, fourth most prominent heading (h4).
- case subheadline = "h4"
- /// Body, fifth most prominent heading (h5).
- case body = "h5"
- /// Footnote, least prominent heading (h6).
- case footnote = "h6"
-}
-
-/// Generates HTML heading elements from `` to ``.
-///
-/// The level of the heading should follow a semantic hierarchy through the document,
-/// with `.title` for the main page title, `.section` for major sections, and
-/// progressively more detailed levels (`.subsection`, `.topic`, etc.) for nested content.
-public final class Heading: Element {
- /// Creates a new heading.
- ///
- /// - Parameters:
- /// - level: Heading level (.largeTitle, .title, .headline, .subheadline, .body, or .footnote).
- /// - id: Unique identifier for the HTML element.
- /// - classes: An array of CSS classnames.
- /// - role: ARIA role of the element for accessibility.
- /// - label: ARIA label to describe the element.
- /// - data: Dictionary of `data-*` attributes for element relevant storing data.
- /// - content: Closure providing heading content.
- public init(
- _ level: HeadingLevel,
- id: String? = nil,
- classes: [String]? = nil,
- role: AriaRole? = nil,
- label: String? = nil,
- data: [String: String]? = nil,
- @HTMLBuilder content: @escaping () -> [any HTML] = { [] }
- ) {
- super.init(
- tag: level.rawValue,
- id: id,
- classes: classes,
- role: role,
- label: label,
- data: data,
- content: content
- )
- }
-}
-
-/// Generates an HTML anchor element; for linking to other locations.
-public final class Link: Element {
- private let href: String
- private let newTab: Bool?
-
- /// Creates a new anchor link.
- ///
- /// - Parameters:
- /// - destination: URL or path the link points to.
- /// - newTab: Opens in a new tab if true, optional.
- /// - id: Unique identifier for the HTML element.
- /// - classes: An array of CSS classnames.
- /// - role: ARIA role of the element for accessibility.
- /// - label: ARIA label to describe the element.
- /// - data: Dictionary of `data-*` attributes for element relevant storing data.
- /// - content: Closure providing link content.
- public init(
- to destination: String,
- newTab: Bool? = nil,
- id: String? = nil,
- classes: [String]? = nil,
- role: AriaRole? = nil,
- label: String? = nil,
- data: [String: String]? = nil,
- @HTMLBuilder content: @escaping () -> [any HTML] = { [] }
- ) {
- self.href = destination
- self.newTab = newTab
-
- var attributes = [Attribute.string("href", destination)].compactMap { $0 }
-
- if newTab == true {
- attributes.append(contentsOf: [
- "target=\"_blank\"",
- "rel=\"noreferrer\"",
- ])
- }
-
- super.init(
- tag: "a",
- id: id,
- classes: classes,
- role: role,
- label: label,
- data: data,
- customAttributes: attributes.isEmpty ? nil : attributes,
- content: content
- )
- }
-}
-
-/// Generates an HTML emphasis element.
-///
-/// To be used to draw attention to text within another body of text.
-public final class Emphasis: Element {
- /// Creates a new emphasis element.
- ///
- /// - Parameters:
- /// - id: Unique identifier for the HTML element.
- /// - classes: An array of CSS classnames.
- /// - role: ARIA role of the element for accessibility.
- /// - label: ARIA label to describe the element.
- /// - data: Dictionary of `data-*` attributes for element relevant storing data.
- /// - content: Closure providing emphasized content.
- public init(
- id: String? = nil,
- classes: [String]? = nil,
- role: AriaRole? = nil,
- label: String? = nil,
- data: [String: String]? = nil,
- @HTMLBuilder content: @escaping () -> [any HTML] = { [] }
- ) {
- super.init(
- tag: "em",
- id: id,
- classes: classes,
- role: role,
- label: label,
- data: data,
- content: content
- )
- }
-}
-
-/// Generates an HTML strong importance element.
-///
-/// To be used for drawing strong attention to text within another body of text.
-public final class Strong: Element {
- /// Creates a new strong element.
- ///
- /// - Parameters:
- /// - id: Unique identifier for the HTML element.
- /// - classes: An array of CSS classnames.
- /// - role: ARIA role of the element for accessibility.
- /// - label: ARIA label to describe the element.
- /// - data: Dictionary of `data-*` attributes for element relevant storing data.
- /// - content: Closure providing strong content.
- public init(
- id: String? = nil,
- classes: [String]? = nil,
- role: AriaRole? = nil,
- label: String? = nil,
- data: [String: String]? = nil,
- @HTMLBuilder content: @escaping () -> [any HTML] = { [] }
- ) {
- super.init(
- tag: "strong",
- id: id,
- classes: classes,
- role: role,
- label: label,
- data: data,
- content: content
- )
- }
-}
-
-/// Generates an HTML time element.
-///
-/// Used to represent a specific date, time, or duration in a machine-readable format.
-/// The datetime attribute provides the machine-readable value while the content
-/// can be a human-friendly representation.
-public final class Time: Element {
- private let datetime: String
-
- /// Creates a new time element.
- ///
- /// - Parameters:
- /// - datetime: Machine-readable date/time in ISO 8601 format (e.g., "2025-03-22" or "2025-03-22T14:30:00Z").
- /// - id: Unique identifier for the HTML element.
- /// - classes: An array of CSS classnames.
- /// - role: ARIA role of the element for accessibility.
- /// - label: ARIA label to describe the element.
- /// - data: Dictionary of `data-*` attributes for element relevant storing data.
- /// - content: Closure providing human-readable time content.
- public init(
- datetime: String,
- id: String? = nil,
- classes: [String]? = nil,
- role: AriaRole? = nil,
- label: String? = nil,
- data: [String: String]? = nil,
- @HTMLBuilder content: @escaping () -> [any HTML] = { [] }
- ) {
- self.datetime = datetime
- let customAttributes = [
- Attribute.string("datetime", datetime)
- ].compactMap { $0 }
- super.init(
- tag: "time",
- id: id,
- classes: classes,
- role: role,
- label: label,
- data: data,
- customAttributes: customAttributes.isEmpty ? nil : customAttributes,
- content: content
- )
- }
-}
-
-/// Generates an HTML code block element
-///
-/// To be used for rendering code examples on a web page
-public final class Code: Element {
- /// Creates a new code element.
- ///
- /// - Parameters:
- /// - id: Unique identifier for the HTML element.
- /// - classes: An array of CSS classnames.
- /// - role: ARIA role of the element for accessibility.
- /// - label: ARIA label to describe the element.
- /// - data: Dictionary of `data-*` attributes for element relevant storing data.
- /// - content: Closure providing code content.
- public init(
- id: String? = nil,
- classes: [String]? = nil,
- role: AriaRole? = nil,
- label: String? = nil,
- data: [String: String]? = nil,
- @HTMLBuilder content: @escaping () -> [any HTML] = { [] }
- ) {
- super.init(
- tag: "code",
- id: id,
- classes: classes,
- role: role,
- label: label,
- data: data,
- content: content
- )
- }
-}
-
-/// Generates an HTML pre element
-///
-/// To be used for rendering preformatted text such as groups of code elements.
-public final class Preformatted: Element {
- /// Creates a new preformatted element.
- ///
- /// - Parameters:
- /// - id: Unique identifier for the HTML element.
- /// - classes: An array of CSS classnames.
- /// - role: ARIA role of the element for accessibility.
- /// - label: ARIA label to describe the element.
- /// - data: Dictionary of `data-*` attributes for element relevant storing data.
- /// - content: Closure providing preformatted content.
- public init(
- id: String? = nil,
- classes: [String]? = nil,
- role: AriaRole? = nil,
- label: String? = nil,
- data: [String: String]? = nil,
- @HTMLBuilder content: @escaping () -> [any HTML] = { [] }
- ) {
- super.init(
- tag: "pre",
- id: id,
- classes: classes,
- role: role,
- label: label,
- data: data,
- content: content
- )
- }
-}
diff --git a/Sources/WebUI/Elements/Base/Button.swift b/Sources/WebUI/Elements/Interactive/Button.swift
similarity index 100%
rename from Sources/WebUI/Elements/Base/Button.swift
rename to Sources/WebUI/Elements/Interactive/Button.swift
diff --git a/Sources/WebUI/Elements/Form/Form.swift b/Sources/WebUI/Elements/Interactive/Form/Form.swift
similarity index 100%
rename from Sources/WebUI/Elements/Form/Form.swift
rename to Sources/WebUI/Elements/Interactive/Form/Form.swift
diff --git a/Sources/WebUI/Elements/Form/Input.swift b/Sources/WebUI/Elements/Interactive/Form/Input/Input.swift
similarity index 84%
rename from Sources/WebUI/Elements/Form/Input.swift
rename to Sources/WebUI/Elements/Interactive/Form/Input/Input.swift
index ea7421d3..2de3484e 100644
--- a/Sources/WebUI/Elements/Form/Input.swift
+++ b/Sources/WebUI/Elements/Interactive/Form/Input/Input.swift
@@ -1,22 +1,3 @@
-/// Defines types for HTML input elements.
-///
-/// Specifies the type of data to be collected by an input element, affecting both
-/// appearance and validation behavior.
-public enum InputType: String {
- /// Single-line text input field for general text entry.
- case text
- /// Masked password input field that hides characters for security.
- case password
- /// Email address input field with validation for email format.
- case email
- /// Numeric input field optimized for number entry, often with increment/decrement controls.
- case number
- /// Checkbox input for boolean (yes/no) selections.
- case checkbox
- /// Submit button input that triggers form submission when clicked.
- case submit
-}
-
/// Generates an HTML input element for collecting user input, such as text or numbers.
///
/// `Input` elements are the primary way to gather user data in forms, supporting various types
diff --git a/Sources/WebUI/Elements/Interactive/Form/Input/InputType.swift b/Sources/WebUI/Elements/Interactive/Form/Input/InputType.swift
new file mode 100644
index 00000000..dcb1db87
--- /dev/null
+++ b/Sources/WebUI/Elements/Interactive/Form/Input/InputType.swift
@@ -0,0 +1,18 @@
+/// Defines types for HTML input elements.
+///
+/// Specifies the type of data to be collected by an input element, affecting both
+/// appearance and validation behavior.
+public enum InputType: String {
+ /// Single-line text input field for general text entry.
+ case text
+ /// Masked password input field that hides characters for security.
+ case password
+ /// Email address input field with validation for email format.
+ case email
+ /// Numeric input field optimized for number entry, often with increment/decrement controls.
+ case number
+ /// Checkbox input for boolean (yes/no) selections.
+ case checkbox
+ /// Submit button input that triggers form submission when clicked.
+ case submit
+}
diff --git a/Sources/WebUI/Elements/Form/Label.swift b/Sources/WebUI/Elements/Interactive/Form/Input/Label.swift
similarity index 100%
rename from Sources/WebUI/Elements/Form/Label.swift
rename to Sources/WebUI/Elements/Interactive/Form/Input/Label.swift
diff --git a/Sources/WebUI/Elements/Form/TextArea.swift b/Sources/WebUI/Elements/Interactive/Form/TextArea.swift
similarity index 100%
rename from Sources/WebUI/Elements/Form/TextArea.swift
rename to Sources/WebUI/Elements/Interactive/Form/TextArea.swift
diff --git a/Sources/WebUI/Elements/Form/Progress.swift b/Sources/WebUI/Elements/Interactive/Progress.swift
similarity index 100%
rename from Sources/WebUI/Elements/Form/Progress.swift
rename to Sources/WebUI/Elements/Interactive/Progress.swift
diff --git a/Sources/WebUI/Elements/Layout/Layout.swift b/Sources/WebUI/Elements/Layout/Layout.swift
deleted file mode 100644
index 65fb28d2..00000000
--- a/Sources/WebUI/Elements/Layout/Layout.swift
+++ /dev/null
@@ -1,273 +0,0 @@
-/// Generates an HTML header element for page or section headers.
-///
-/// The `Header` element represents a container for introductory content or a set of navigational links
-/// at the beginning of a section or page. Typically contains elements like site logos, navigation menus,
-/// and search forms.
-///
-/// ## Example
-/// ```swift
-/// Header {
-/// Heading(.largeTitle) { "Site Title" }
-/// Navigation {
-/// Link(to: "/home") { "Home" }
-/// Link(to: "/about") { "About" }
-/// }
-/// }
-/// ```
-public final class Header: Element {
- /// Creates a new HTML header element.
- ///
- /// - Parameters:
- /// - id: Unique identifier for the HTML element, useful for styling and scripting.
- /// - classes: An array of CSS classnames for styling the header.
- /// - role: ARIA role of the element for accessibility and screen readers.
- /// - label: ARIA label to describe the element for screen readers.
- /// - data: Dictionary of `data-*` attributes for storing custom data related to the header.
- /// - content: Closure providing header content like headings, navigation, and logos.
- ///
- /// ## Example
- /// ```swift
- /// Header(id: "main-header", classes: ["site-header", "sticky"]) {
- /// Heading(.largeTitle) { "My Website" }
- /// }
- /// ```
- public init(
- id: String? = nil,
- classes: [String]? = nil,
- role: AriaRole? = nil,
- label: String? = nil,
- data: [String: String]? = nil,
- @HTMLBuilder content: @escaping () -> [any HTML] = { [] }
- ) {
- super.init(
- tag: "header",
- id: id,
- classes: classes,
- role: role,
- label: label,
- data: data,
- content: content
- )
- }
-}
-
-/// Generates an HTML navigation element for site navigation.
-///
-/// The `Navigation` element represents a section of a page intended to contain navigation
-/// links to other pages or parts within the current page. It helps screen readers and
-/// other assistive technologies identify the main navigation structure of the website.
-///
-/// ## Example
-/// ```swift
-/// Navigation(classes: ["main-nav"]) {
-/// List {
-/// Item { Link(to: "/") { "Home" } }
-/// Item { Link(to: "/products") { "Products" } }
-/// Item { Link(to: "/contact") { "Contact" } }
-/// }
-/// }
-/// ```
-public final class Navigation: Element {
- /// Creates a new HTML navigation element.
- ///
- /// - Parameters:
- /// - id: Unique identifier for the HTML element, useful for styling and scripting.
- /// - classes: An array of CSS classnames for styling the navigation container.
- /// - role: ARIA role of the element for accessibility and screen readers.
- /// - label: ARIA label to describe the element's purpose (e.g., "Main Navigation").
- /// - data: Dictionary of `data-*` attributes for storing custom data related to navigation.
- /// - content: Closure providing navigation content, typically links or lists of links.
- ///
- /// ## Example
- /// ```swift
- /// Navigation(id: "main-nav", label: "Main Navigation") {
- /// Link(to: "/home") { "Home" }
- /// Link(to: "/about") { "About Us" }
- /// }
- /// ```
- public init(
- id: String? = nil,
- classes: [String]? = nil,
- role: AriaRole? = nil,
- label: String? = nil,
- data: [String: String]? = nil,
- @HTMLBuilder content: @escaping () -> [any HTML] = { [] }
- ) {
- super.init(
- tag: "nav",
- id: id,
- classes: classes,
- role: role,
- label: label,
- data: data,
- content: content
- )
- }
-}
-
-/// Generates an HTML aside element for tangentially related content.
-///
-/// The `Aside` element represents a section of content that is indirectly related to the
-/// main content but could be considered separate. Asides are typically displayed as
-/// sidebars or call-out boxes, containing content like related articles, glossary terms,
-/// advertisements, or author biographies.
-///
-/// ## Example
-/// ```swift
-/// Aside(classes: ["sidebar"]) {
-/// Heading(.title) { "Related Articles" }
-/// List {
-/// Item { Link(to: "/article1") { "Article 1" } }
-/// Item { Link(to: "/article2") { "Article 2" } }
-/// }
-/// }
-/// ```
-public final class Aside: Element {
- /// Creates a new HTML aside element.
- ///
- /// - Parameters:
- /// - id: Unique identifier for the HTML element, useful for styling and scripting.
- /// - classes: An array of CSS classnames for styling the aside container.
- /// - role: ARIA role of the element for accessibility and screen readers.
- /// - label: ARIA label to describe the element's purpose (e.g., "Related Content").
- /// - data: Dictionary of `data-*` attributes for storing custom data related to the aside.
- /// - content: Closure providing aside content, such as related links, footnotes, or supplementary information.
- ///
- /// ## Example
- /// ```swift
- /// Aside(id: "glossary", classes: ["note", "bordered"], label: "Term Definition") {
- /// Heading(.headline) { "Definition" }
- /// Text { "A detailed explanation of the term..." }
- /// }
- /// ```
- public init(
- id: String? = nil,
- classes: [String]? = nil,
- role: AriaRole? = nil,
- label: String? = nil,
- data: [String: String]? = nil,
- @HTMLBuilder content: @escaping () -> [any HTML] = { [] }
- ) {
- super.init(
- tag: "aside",
- id: id,
- classes: classes,
- role: role,
- label: label,
- data: data,
- content: content
- )
- }
-}
-
-/// Generates an HTML main element for the primary content of a page.
-///
-/// The `Main` element represents the dominant content of the document body. It contains content
-/// that is directly related to or expands upon the central topic of the document. Each page
-/// should have only one `main` element, which helps assistive technologies navigate to the
-/// primary content.
-///
-/// ## Example
-/// ```swift
-/// Main {
-/// Heading(.largeTitle) { "Welcome to Our Website" }
-/// Text { "This is the main content of our homepage." }
-/// Article {
-/// // Article content
-/// }
-/// }
-/// ```
-public final class Main: Element {
- /// Creates a new HTML main element.
- ///
- /// - Parameters:
- /// - id: Unique identifier for the HTML element, useful for styling and scripting.
- /// - classes: An array of CSS classnames for styling the main content area.
- /// - role: ARIA role of the element for accessibility and screen readers.
- /// - label: ARIA label to describe the element's purpose (e.g., "Main Content").
- /// - data: Dictionary of `data-*` attributes for storing custom data related to the main content.
- /// - content: Closure providing the primary content of the page, typically including articles, sections, and other content elements.
- ///
- /// ## Example
- /// ```swift
- /// Main(id: "content", classes: ["container"]) {
- /// Section {
- /// Heading(.largeTitle) { "About Us" }
- /// Text { "Learn more about our company history..." }
- /// }
- /// }
- /// ```
- public init(
- id: String? = nil,
- classes: [String]? = nil,
- role: AriaRole? = nil,
- label: String? = nil,
- data: [String: String]? = nil,
- @HTMLBuilder content: @escaping () -> [any HTML] = { [] }
- ) {
- super.init(
- tag: "main",
- id: id,
- classes: classes,
- role: role,
- label: label,
- data: data,
- content: content
- )
- }
-}
-
-/// Generates an HTML footer element for page or section footers.
-///
-/// The `Footer` element represents a footer for its nearest sectioning content or sectioning root
-/// element. A footer typically contains information about the author, copyright data, related links,
-/// legal information, and other metadata that appears at the end of a document or section.
-///
-/// ## Example
-/// ```swift
-/// Footer {
-/// Text { "© 2023 My Company. All rights reserved." }
-/// Link(to: "/privacy") { "Privacy Policy" }
-/// Link(to: "/terms") { "Terms of Service" }
-/// }
-/// ```
-public final class Footer: Element {
- /// Creates a new HTML footer element.
- ///
- /// - Parameters:
- /// - id: Unique identifier for the HTML element, useful for styling and scripting.
- /// - classes: An array of CSS classnames for styling the footer.
- /// - role: ARIA role of the element for accessibility and screen readers.
- /// - label: ARIA label to describe the element's purpose (e.g., "Page Footer").
- /// - data: Dictionary of `data-*` attributes for storing custom data related to the footer.
- /// - content: Closure providing footer content, such as copyright notices, contact information, and secondary navigation.
- ///
- /// ## Example
- /// ```swift
- /// Footer(id: "site-footer", classes: ["footer", "bg-dark"]) {
- /// Stack(classes: ["footer-links"]) {
- /// Link(to: "/about") { "About" }
- /// Link(to: "/contact") { "Contact" }
- /// }
- /// Text { "© \(Date().formattedYear()) My Company" }
- /// }
- /// ```
- public init(
- id: String? = nil,
- classes: [String]? = nil,
- role: AriaRole? = nil,
- label: String? = nil,
- data: [String: String]? = nil,
- @HTMLBuilder content: @escaping () -> [any HTML] = { [] }
- ) {
- super.init(
- tag: "footer",
- id: id,
- classes: classes,
- role: role,
- label: label,
- data: data,
- content: content
- )
- }
-}
diff --git a/Sources/WebUI/Elements/Layout/Structure.swift b/Sources/WebUI/Elements/Layout/Structure.swift
deleted file mode 100644
index 073d9069..00000000
--- a/Sources/WebUI/Elements/Layout/Structure.swift
+++ /dev/null
@@ -1,162 +0,0 @@
-/// Generates an HTML article element for self-contained content sections.
-///
-/// Represents a self-contained, independently distributable composition like a blog post,
-/// news story, forum post, or any content that could stand alone. Articles are ideal for
-/// content that could be syndicated or reused elsewhere.
-///
-/// ## Example
-/// ```swift
-/// Article {
-/// Heading(.largeTitle) { "Blog Post Title" }
-/// Text { "Published on May 15, 2023" }
-/// Text { "This is the content of the blog post..." }
-/// }
-/// ```
-public final class Article: Element {
- /// Creates a new HTML article element for self-contained content.
- ///
- /// - Parameters:
- /// - id: Unique identifier for the HTML element, useful for linking and scripting.
- /// - classes: An array of CSS classnames for styling the article.
- /// - role: ARIA role of the element for accessibility and screen readers.
- /// - label: ARIA label to describe the element for screen readers.
- /// - data: Dictionary of `data-*` attributes for storing custom data related to the article.
- /// - content: Closure providing article content such as headings, paragraphs, and media.
- ///
- /// ## Example
- /// ```swift
- /// Article(id: "post-123", classes: ["blog-post", "featured"]) {
- /// Heading(.largeTitle) { "Getting Started with WebUI" }
- /// Text { "Learn how to build static websites using Swift..." }
- /// }
- /// ```
- public init(
- id: String? = nil,
- classes: [String]? = nil,
- role: AriaRole? = nil,
- label: String? = nil,
- data: [String: String]? = nil,
- @HTMLBuilder content: @escaping () -> [any HTML] = { [] }
- ) {
- super.init(
- tag: "article",
- id: id,
- classes: classes,
- role: role,
- label: label,
- data: data,
- content: content
- )
- }
-}
-
-/// Generates an HTML section element for thematic content grouping.
-///
-/// Defines a thematic grouping of content, such as a chapter, tab panel, or any content
-/// that forms a distinct section of a document. Sections typically have their own heading
-/// and represent a logical grouping of related content.
-///
-/// ## Example
-/// ```swift
-/// Section(id: "features") {
-/// Heading(.title) { "Key Features" }
-/// List {
-/// Item { "Simple API" }
-/// Item { "Type-safe HTML generation" }
-/// Item { "Responsive design" }
-/// }
-/// }
-/// ```
-public final class Section: Element {
- /// Creates a new HTML section element for thematic content grouping.
- ///
- /// - Parameters:
- /// - id: Unique identifier for the HTML element, useful for navigation and linking.
- /// - classes: An array of CSS classnames for styling the section.
- /// - role: ARIA role of the element for accessibility and screen readers.
- /// - label: ARIA label to describe the element's purpose for screen readers.
- /// - data: Dictionary of `data-*` attributes for storing custom data related to the section.
- /// - content: Closure providing section content such as headings, paragraphs, and other elements.
- ///
- /// ## Example
- /// ```swift
- /// Section(id: "about", classes: ["content-section"]) {
- /// Heading(.title) { "About Us" }
- /// Text { "Our company was founded in 2020..." }
- /// }
- /// ```
- public init(
- id: String? = nil,
- classes: [String]? = nil,
- role: AriaRole? = nil,
- label: String? = nil,
- data: [String: String]? = nil,
- @HTMLBuilder content: @escaping () -> [any HTML] = { [] }
- ) {
- super.init(
- tag: "section",
- id: id,
- classes: classes,
- role: role,
- label: label,
- data: data,
- content: content
- )
- }
-}
-
-/// Generates an HTML div element for generic content grouping.
-///
-/// The `Stack` element (which renders as a div) groups elements for styling or layout
-/// without conveying any specific semantic meaning. It's a versatile container used
-/// for creating layout structures, applying styles to groups, or providing hooks for
-/// JavaScript functionality.
-///
-/// - Note: Use semantic elements like `Article`, `Section`, or `Aside` when possible,
-/// and reserve `Stack` for purely presentational grouping.
-///
-/// ## Example
-/// ```swift
-/// Stack(classes: ["flex-container"]) {
-/// Stack(classes: ["card"]) { "Card 1 content" }
-/// Stack(classes: ["card"]) { "Card 2 content" }
-/// }
-/// ```
-public final class Stack: Element {
- /// Creates a new HTML div element for generic content grouping.
- ///
- /// - Parameters:
- /// - id: Unique identifier for the HTML element, useful for styling and scripting.
- /// - classes: An array of CSS classnames for styling the div container.
- /// - role: ARIA role of the element for accessibility and screen readers.
- /// - label: ARIA label to describe the element's purpose for screen readers.
- /// - data: Dictionary of `data-*` attributes for storing custom data related to the container.
- /// - content: Closure providing the container's content elements.
- ///
- /// ## Example
- /// ```swift
- /// Stack(id: "user-profile", classes: ["card", "shadow"], data: ["user-id": "123"]) {
- /// Image(source: "/avatar.jpg", description: "User Avatar")
- /// Heading(.headline) { "Jane Doe" }
- /// Text { "Software Engineer" }
- /// }
- /// ```
- public init(
- id: String? = nil,
- classes: [String]? = nil,
- role: AriaRole? = nil,
- label: String? = nil,
- data: [String: String]? = nil,
- @HTMLBuilder content: @escaping () -> [any HTML] = { [] }
- ) {
- super.init(
- tag: "div",
- id: id,
- classes: classes,
- role: role,
- label: label,
- data: data,
- content: content
- )
- }
-}
diff --git a/Sources/WebUI/Elements/Media/Audio/Audio.swift b/Sources/WebUI/Elements/Media/Audio/Audio.swift
new file mode 100644
index 00000000..3567463e
--- /dev/null
+++ b/Sources/WebUI/Elements/Media/Audio/Audio.swift
@@ -0,0 +1,83 @@
+/// Creates HTML audio elements for playing sound content.
+///
+/// Represents an audio player that supports multiple source formats for cross-browser compatibility.
+/// Audio elements are useful for embedding sound content such as music, podcasts, or sound effects.
+///
+/// ## Example
+/// ```swift
+/// Audio(
+/// sources: [
+/// ("background.mp3", .mp3),
+/// ("background.ogg", .ogg)
+/// ],
+/// controls: true
+/// )
+/// ```
+public final class Audio: Element {
+ let sources: [(src: String, type: AudioType?)]
+ let controls: Bool?
+ let autoplay: Bool?
+ let loop: Bool?
+
+ /// Creates a new HTML audio player.
+ ///
+ /// - Parameters:
+ /// - sources: Array of tuples containing source URL and optional audio MIME type.
+ /// - controls: Displays playback controls if true, optional.
+ /// - autoplay: Automatically starts playback if true, optional.
+ /// - loop: Repeats audio playback if true, optional.
+ /// - id: Unique identifier for the HTML element, useful for JavaScript interaction and styling.
+ /// - classes: An array of CSS classnames for styling the audio player.
+ /// - role: ARIA role of the element for accessibility, enhancing screen reader interpretation.
+ /// - label: ARIA label to describe the element for accessibility when context isn't sufficient.
+ /// - data: Dictionary of `data-*` attributes for storing custom data relevant to the audio player.
+ ///
+ /// ## Example
+ /// ```swift
+ /// Audio(
+ /// sources: [
+ /// ("podcast.mp3", .mp3),
+ /// ("podcast.ogg", .ogg)
+ /// ],
+ /// controls: true,
+ /// id: "podcast-player",
+ /// label: "Episode 42: Web Development with Swift"
+ /// )
+ /// ```
+ public init(
+ sources: [(src: String, type: AudioType?)],
+ controls: Bool? = nil,
+ autoplay: Bool? = nil,
+ loop: Bool? = nil,
+ id: String? = nil,
+ classes: [String]? = nil,
+ role: AriaRole? = nil,
+ label: String? = nil,
+ data: [String: String]? = nil
+ ) {
+ self.sources = sources
+ self.controls = controls
+ self.autoplay = autoplay
+ self.loop = loop
+ let customAttributes = [
+ Attribute.bool("controls", controls),
+ Attribute.bool("autoplay", autoplay),
+ Attribute.bool("loop", loop),
+ ].compactMap { $0 }
+ super.init(
+ tag: "audio",
+ id: id,
+ classes: classes,
+ role: role,
+ label: label,
+ data: data,
+ customAttributes: customAttributes.isEmpty ? nil : customAttributes,
+ content: {
+ for source in sources {
+ Source(src: source.src, type: source.type?.rawValue)
+ }
+ "Your browser does not support the audio element."
+ }
+ )
+ }
+}
diff --git a/Sources/WebUI/Elements/Media/Audio/AudioType.swift b/Sources/WebUI/Elements/Media/Audio/AudioType.swift
new file mode 100644
index 00000000..f2e0d359
--- /dev/null
+++ b/Sources/WebUI/Elements/Media/Audio/AudioType.swift
@@ -0,0 +1,21 @@
+/// Defines audio MIME types for use with audio elements.
+///
+/// Used to specify the format of audio files, ensuring browsers can properly interpret and play the content.
+/// Different browsers support different audio formats, so providing multiple source types improves compatibility.
+///
+/// ## Example
+/// ```swift
+/// Audio(
+/// sources: [
+/// ("music.mp3", .mp3),
+/// ("music.ogg", .ogg)
+/// ],
+/// controls: true,
+/// loop: true
+/// )
+/// ```
+public enum AudioType: String {
+ case mp3 = "audio/mpeg"
+ case ogg = "audio/ogg"
+ case wav = "audio/wav"
+}
diff --git a/Sources/WebUI/Elements/Media/Image/Figure.swift b/Sources/WebUI/Elements/Media/Image/Figure.swift
new file mode 100644
index 00000000..179ee520
--- /dev/null
+++ b/Sources/WebUI/Elements/Media/Image/Figure.swift
@@ -0,0 +1,84 @@
+/// Generates an HTML figure element with a picture and figcaption.
+/// Styles and attributes applied to this element are passed to the nested Picture element,
+/// which further passes them to its nested Image element.
+///
+/// ## Example
+/// ```swift
+/// Figure(
+/// sources: [
+/// ("chart.webp", .webp),
+/// ("chart.png", .png)
+/// ],
+/// description: "Annual revenue growth chart"
+/// )
+/// ```
+public final class Figure: Element {
+ let sources: [(src: String, type: ImageType?)]
+ let description: String
+ let size: MediaSize?
+
+ /// Creates a new HTML figure element containing a picture and figcaption.
+ ///
+ /// - Parameters:
+ /// - sources: Array of tuples containing source URL and optional image MIME type.
+ /// - description: Text for the figcaption and alt text for accessibility.
+ /// - size: Picture size dimensions, optional.
+ /// - id: Unique identifier for the HTML element.
+ /// - classes: An array of CSS classnames.
+ /// - role: ARIA role of the element for accessibility.
+ /// - label: ARIA label to describe the element.
+ /// - data: Dictionary of `data-*` attributes for element relevant storing data.
+ ///
+ /// All style attributes (id, classes, role, label, data) are passed to the nested Picture element
+ /// and ultimately to the Image element, ensuring proper styling throughout the hierarchy.
+ ///
+ /// ## Example
+ /// ```swift
+ /// Figure(
+ /// sources: [
+ /// ("product.webp", .webp),
+ /// ("product.jpg", .jpeg)
+ /// ],
+ /// description: "Product XYZ with special features",
+ /// classes: ["product-figure", "bordered"]
+ /// )
+ /// ```
+ public init(
+ sources: [(src: String, type: ImageType?)],
+ description: String,
+ size: MediaSize? = nil,
+ id: String? = nil,
+ classes: [String]? = nil,
+ role: AriaRole? = nil,
+ label: String? = nil,
+ data: [String: String]? = nil
+ ) {
+ self.sources = sources
+ self.description = description
+ self.size = size
+ super.init(
+ tag: "figure",
+ id: id,
+ classes: classes,
+ role: role,
+ label: label,
+ data: data,
+ content: {
+ Picture(
+ sources: sources,
+ description: description,
+ size: size,
+ id: id,
+ classes: classes,
+ role: role,
+ label: label,
+ data: data
+ )
+ Element(
+ tag: "figcaption",
+ content: { description }
+ )
+ }
+ )
+ }
+}
diff --git a/Sources/WebUI/Elements/Media/Image/Image.swift b/Sources/WebUI/Elements/Media/Image/Image.swift
new file mode 100644
index 00000000..5497a67c
--- /dev/null
+++ b/Sources/WebUI/Elements/Media/Image/Image.swift
@@ -0,0 +1,58 @@
+import Foundation
+
+/// Creates HTML image elements for displaying graphical content.
+///
+/// Represents an image that can be embedded in a webpage, with support for accessibility
+/// descriptions and sizing information. Images are fundamental for illustrating content
+/// and enhancing visual communication.
+///
+/// ## Example
+/// ```swift
+/// Image(
+/// source: "logo.png",
+/// description: "Company Logo",
+/// type: .png,
+/// size: MediaSize(width: 100, height: 100)
+/// )
+/// ```
+public final class Image: Element {
+ /// Creates a new HTML image element.
+ ///
+ /// - Parameters:
+ /// - source: The image source URL or path.
+ /// - description: The alt text for the image for accessibility and SEO.
+ /// - type: The MIME type of the image, optional.
+ /// - size: The size of the image in pixels, optional.
+ /// - id: Unique identifier for the HTML element, useful for JavaScript interaction and styling.
+ /// - classes: An array of CSS classnames for styling the image.
+ /// - role: ARIA role of the element for accessibility, enhancing screen reader interpretation.
+ /// - label: ARIA label to describe the element for accessibility when alt text isn't sufficient.
+ /// - data: Dictionary of `data-*` attributes for storing custom data relevant to the image.
+ public init(
+ source: String,
+ description: String,
+ type: ImageType? = nil,
+ size: MediaSize? = nil,
+ id: String? = nil,
+ classes: [String]? = nil,
+ role: AriaRole? = nil,
+ label: String? = nil,
+ data: [String: String]? = nil
+ ) {
+ var attributes: [String] = ["src=\"\(source)\"", "alt=\"\(description)\""]
+ if let type = type { attributes.append("type=\"\(type.rawValue)\"") }
+ if let size = size {
+ if let width = size.width { attributes.append("width=\"\(width)\"") }
+ if let height = size.height { attributes.append("height=\"\(height)\"") }
+ }
+ super.init(
+ tag: "img",
+ id: id,
+ classes: classes,
+ role: role,
+ label: label,
+ data: data,
+ customAttributes: attributes
+ )
+ }
+}
diff --git a/Sources/WebUI/Elements/Media/Image/ImageType.swift b/Sources/WebUI/Elements/Media/Image/ImageType.swift
new file mode 100644
index 00000000..6cee7c3b
--- /dev/null
+++ b/Sources/WebUI/Elements/Media/Image/ImageType.swift
@@ -0,0 +1,21 @@
+import Foundation
+
+/// Defines image MIME types for use with image elements.
+///
+/// Used to specify the format of image files, ensuring browsers can properly interpret and display the content.
+/// Different browsers support different image formats, so providing multiple source types improves compatibility.
+///
+/// ## Example
+/// ```swift
+/// Image(
+/// source: "photo.jpg",
+/// description: "A beautiful landscape",
+/// type: .jpeg
+/// )
+/// ```
+public enum ImageType: String {
+ case jpeg = "image/jpeg"
+ case png = "image/png"
+ case webp = "image/webp"
+ case gif = "image/gif"
+}
diff --git a/Sources/WebUI/Elements/Media/Image/Picture.swift b/Sources/WebUI/Elements/Media/Image/Picture.swift
new file mode 100644
index 00000000..45ecc250
--- /dev/null
+++ b/Sources/WebUI/Elements/Media/Image/Picture.swift
@@ -0,0 +1,79 @@
+/// Generates an HTML picture element with multiple source tags.
+/// Styles and attributes applied to this element are also passed to the nested Image element.
+///
+/// ## Example
+/// ```swift
+/// Picture(
+/// sources: [
+/// ("banner.webp", .webp),
+/// ("banner.jpg", .jpeg)
+/// ],
+/// description: "Website banner image"
+/// )
+/// ```
+public final class Picture: Element {
+ let sources: [(src: String, type: ImageType?)]
+ let description: String
+ let size: MediaSize?
+
+ /// Creates a new HTML picture element.
+ ///
+ /// - Parameters:
+ /// - sources: Array of tuples containing source URL and optional image MIME type.
+ /// - description: Alt text for accessibility.
+ /// - size: Picture size dimensions, optional.
+ /// - id: Unique identifier for the HTML element.
+ /// - classes: An array of CSS classnames.
+ /// - role: ARIA role of the element for accessibility.
+ /// - label: ARIA label to describe the element.
+ /// - data: Dictionary of `data-*` attributes for element relevant storing data.
+ ///
+ /// All style attributes (id, classes, role, label, data) are passed to the nested Image element
+ /// to ensure proper styling, as the Picture element itself is invisible in the rendered output.
+ ///
+ /// ## Example
+ /// ```swift
+ /// Picture(
+ /// sources: [
+ /// ("hero-large.webp", .webp),
+ /// ("hero-large.jpg", .jpeg)
+ /// ],
+ /// description: "Hero Banner",
+ /// id: "hero-image",
+ /// classes: ["responsive-image"]
+ /// )
+ /// ```
+ public init(
+ sources: [(src: String, type: ImageType?)],
+ description: String,
+ size: MediaSize? = nil,
+ id: String? = nil,
+ classes: [String]? = nil,
+ role: AriaRole? = nil,
+ label: String? = nil,
+ data: [String: String]? = nil
+ ) {
+ self.sources = sources
+ self.description = description
+ self.size = size
+ super.init(
+ tag: "picture",
+ id: id,
+ classes: classes,
+ role: role,
+ label: label,
+ data: data,
+ content: {
+ for source in sources {
+ Source(src: source.src, type: source.type?.rawValue)
+ }
+ Image(
+ source: sources[0].src,
+ description: description,
+ size: size,
+ data: data
+ )
+ }
+ )
+ }
+}
diff --git a/Sources/WebUI/Elements/Media/MediaSize.swift b/Sources/WebUI/Elements/Media/MediaSize.swift
new file mode 100644
index 00000000..4d823049
--- /dev/null
+++ b/Sources/WebUI/Elements/Media/MediaSize.swift
@@ -0,0 +1,29 @@
+import Foundation
+
+/// Specifies dimensions for media elements such as images, videos, and audio visualizations.
+///
+/// This struct provides a consistent way to define width and height measurements for media elements,
+/// helping ensure proper rendering and layout in HTML. Both dimensions are optional to accommodate
+/// scenarios where only one dimension needs to be specified while maintaining aspect ratio.
+///
+/// ## Example
+/// ```swift
+/// let size = MediaSize(width: 800, height: 600)
+/// Video(sources: [("movie.mp4", .mp4)], size: size)
+/// ```
+public struct MediaSize {
+ /// The width of the media in pixels.
+ public let width: Int?
+ /// The height of the media in pixels.
+ public let height: Int?
+
+ /// Creates a new media size specification.
+ ///
+ /// - Parameters:
+ /// - width: Width dimension in pixels, optional.
+ /// - height: Height dimension in pixels, optional.
+ public init(width: Int? = nil, height: Int? = nil) {
+ self.width = width
+ self.height = height
+ }
+}
diff --git a/Sources/WebUI/Elements/Media/Source.swift b/Sources/WebUI/Elements/Media/Source.swift
new file mode 100644
index 00000000..1388a679
--- /dev/null
+++ b/Sources/WebUI/Elements/Media/Source.swift
@@ -0,0 +1,64 @@
+import Foundation
+
+/// Creates HTML source elements for multimedia content.
+///
+/// Specifies multiple media resources for the ``, ``, and `` elements,
+/// allowing browsers to choose the most suitable format based on device capabilities, network
+/// conditions, and user preferences. Source elements facilitate responsive media delivery,
+/// progressive enhancement, and backward compatibility across different browsers and devices.
+///
+/// ## Example
+/// ```swift
+/// Video {
+/// Source(src: "movie.webm", type: "video/webm")
+/// Source(src: "movie.mp4", type: "video/mp4")
+/// "Your browser does not support the video tag."
+/// }
+/// ```
+public final class Source: Element {
+ let src: String
+ let type: String?
+
+ /// Creates a new HTML source element.
+ ///
+ /// - Parameters:
+ /// - src: The URL of the media resource. Can be absolute or relative path.
+ /// - type: The MIME type of the media resource, helping browsers determine compatibility
+ /// before downloading. Examples include "video/mp4", "audio/mpeg", "image/webp".
+ /// - data: Dictionary of `data-*` attributes for storing custom data relevant to the source.
+ ///
+ /// ## Example
+ /// ```swift
+ /// // For responsive images in a picture element
+ /// Picture {
+ /// Source(src: "large.jpg", type: "image/jpeg", data: ["media": "(min-width: 800px)"])
+ /// Source(src: "medium.jpg", type: "image/jpeg", data: ["media": "(min-width: 600px)"])
+ /// Image(src: "small.jpg", alt: "A description of the image")
+ /// }
+ ///
+ /// // For audio with multiple formats
+ /// Audio {
+ /// Source(src: "audio.ogg", type: "audio/ogg")
+ /// Source(src: "audio.mp3", type: "audio/mpeg")
+ /// "Your browser does not support the audio element."
+ /// }
+ /// ```
+ public init(
+ src: String,
+ type: String? = nil,
+ data: [String: String]? = nil
+ ) {
+ self.src = src
+ self.type = type
+ let customAttributes = [
+ Attribute.string("src", src),
+ Attribute.string("type", type),
+ ].compactMap { $0 }
+ super.init(
+ tag: "source",
+ data: data,
+ isSelfClosing: true,
+ customAttributes: customAttributes.isEmpty ? nil : customAttributes
+ )
+ }
+}
diff --git a/Sources/WebUI/Elements/Media/Video/Video.swift b/Sources/WebUI/Elements/Media/Video/Video.swift
new file mode 100644
index 00000000..039c1587
--- /dev/null
+++ b/Sources/WebUI/Elements/Media/Video/Video.swift
@@ -0,0 +1,94 @@
+import Foundation
+
+/// Creates HTML video elements for displaying video content.
+///
+/// Represents a video player that supports multiple source formats for cross-browser compatibility.
+/// Video elements are fundamental for embedding video content such as tutorials, presentations, or promotional material.
+///
+/// ## Example
+/// ```swift
+/// Video(
+/// sources: [
+/// ("intro.webm", .webm),
+/// ("intro.mp4", .mp4)
+/// ],
+/// controls: true,
+/// autoplay: false
+/// )
+/// ```
+public final class Video: Element {
+ let sources: [(src: String, type: VideoType?)]
+ let controls: Bool?
+ let autoplay: Bool?
+ let loop: Bool?
+ let size: MediaSize?
+
+ /// Creates a new HTML video player.
+ ///
+ /// - Parameters:
+ /// - sources: Array of tuples containing source URL and optional video MIME type.
+ /// - controls: Displays playback controls if true, optional.
+ /// - autoplay: Automatically starts playback if true, optional.
+ /// - loop: Repeats video playback if true, optional.
+ /// - size: Video size dimensions, optional.
+ /// - id: Unique identifier for the HTML element, useful for JavaScript interaction and styling.
+ /// - classes: An array of CSS classnames for styling the video player.
+ /// - role: ARIA role of the element for accessibility, enhancing screen reader interpretation.
+ /// - label: ARIA label to describe the element for accessibility when context isn't sufficient.
+ /// - data: Dictionary of `data-*` attributes for storing custom data relevant to the video player.
+ ///
+ /// ## Example
+ /// ```swift
+ /// Video(
+ /// sources: [
+ /// ("tutorial.webm", .webm),
+ /// ("tutorial.mp4", .mp4)
+ /// ],
+ /// controls: true,
+ /// loop: true,
+ /// size: MediaSize(width: 1280, height: 720),
+ /// id: "tutorial-video",
+ /// classes: ["responsive-video"]
+ /// )
+ /// ```
+ public init(
+ sources: [(src: String, type: VideoType?)],
+ controls: Bool? = nil,
+ autoplay: Bool? = nil,
+ loop: Bool? = nil,
+ size: MediaSize? = nil,
+ id: String? = nil,
+ classes: [String]? = nil,
+ role: AriaRole? = nil,
+ label: String? = nil,
+ data: [String: String]? = nil
+ ) {
+ self.sources = sources
+ self.controls = controls
+ self.autoplay = autoplay
+ self.loop = loop
+ self.size = size
+ let customAttributes = [
+ Attribute.bool("controls", controls),
+ Attribute.bool("autoplay", autoplay),
+ Attribute.bool("loop", loop),
+ Attribute.string("width", size?.width?.description),
+ Attribute.string("height", size?.height?.description),
+ ].compactMap { $0 }
+ super.init(
+ tag: "video",
+ id: id,
+ classes: classes,
+ role: role,
+ label: label,
+ data: data,
+ customAttributes: customAttributes.isEmpty ? nil : customAttributes,
+ content: {
+ for source in sources {
+ Source(src: source.src, type: source.type?.rawValue)
+ }
+ "Your browser does not support the video tag."
+ }
+ )
+ }
+}
diff --git a/Sources/WebUI/Elements/Media/Video/VideoType.swift b/Sources/WebUI/Elements/Media/Video/VideoType.swift
new file mode 100644
index 00000000..88e05248
--- /dev/null
+++ b/Sources/WebUI/Elements/Media/Video/VideoType.swift
@@ -0,0 +1,22 @@
+import Foundation
+
+/// Defines video MIME types for use with video elements.
+///
+/// Used to specify the format of video files, ensuring browsers can properly interpret and play the content.
+/// Different browsers support different video formats, so providing multiple source types improves compatibility.
+///
+/// ## Example
+/// ```swift
+/// Video(
+/// sources: [
+/// (src: "movie.mp4", type: .mp4),
+/// (src: "movie.webm", type: .webm)
+/// ],
+/// controls: true
+/// )
+/// ```
+public enum VideoType: String {
+ case mp4 = "video/mp4"
+ case webm = "video/webm"
+ case ogg = "video/ogg"
+}
diff --git a/Sources/WebUI/Elements/Structure/Layout/Article.swift b/Sources/WebUI/Elements/Structure/Layout/Article.swift
new file mode 100644
index 00000000..1776cc40
--- /dev/null
+++ b/Sources/WebUI/Elements/Structure/Layout/Article.swift
@@ -0,0 +1,51 @@
+/// Generates an HTML article element for self-contained content sections.
+///
+/// Represents a self-contained, independently distributable composition like a blog post,
+/// news story, forum post, or any content that could stand alone. Articles are ideal for
+/// content that could be syndicated or reused elsewhere.
+///
+/// ## Example
+/// ```swift
+/// Article {
+/// Heading(.largeTitle) { "Blog Post Title" }
+/// Text { "Published on May 15, 2023" }
+/// Text { "This is the content of the blog post..." }
+/// }
+/// ```
+public final class Article: Element {
+ /// Creates a new HTML article element for self-contained content.
+ ///
+ /// - Parameters:
+ /// - id: Unique identifier for the HTML element, useful for linking and scripting.
+ /// - classes: An array of CSS classnames for styling the article.
+ /// - role: ARIA role of the element for accessibility and screen readers.
+ /// - label: ARIA label to describe the element for screen readers.
+ /// - data: Dictionary of `data-*` attributes for storing custom data related to the article.
+ /// - content: Closure providing article content such as headings, paragraphs, and media.
+ ///
+ /// ## Example
+ /// ```swift
+ /// Article(id: "post-123", classes: ["blog-post", "featured"]) {
+ /// Heading(.largeTitle) { "Getting Started with WebUI" }
+ /// Text { "Learn how to build static websites using Swift..." }
+ /// }
+ /// ```
+ public init(
+ id: String? = nil,
+ classes: [String]? = nil,
+ role: AriaRole? = nil,
+ label: String? = nil,
+ data: [String: String]? = nil,
+ @HTMLBuilder content: @escaping () -> [any HTML] = { [] }
+ ) {
+ super.init(
+ tag: "article",
+ id: id,
+ classes: classes,
+ role: role,
+ label: label,
+ data: data,
+ content: content
+ )
+ }
+}
diff --git a/Sources/WebUI/Elements/Structure/Layout/Aside.swift b/Sources/WebUI/Elements/Structure/Layout/Aside.swift
new file mode 100644
index 00000000..0423eb06
--- /dev/null
+++ b/Sources/WebUI/Elements/Structure/Layout/Aside.swift
@@ -0,0 +1,54 @@
+/// Generates an HTML aside element for tangentially related content.
+///
+/// The `Aside` element represents a section of content that is indirectly related to the
+/// main content but could be considered separate. Asides are typically displayed as
+/// sidebars or call-out boxes, containing content like related articles, glossary terms,
+/// advertisements, or author biographies.
+///
+/// ## Example
+/// ```swift
+/// Aside(classes: ["sidebar"]) {
+/// Heading(.title) { "Related Articles" }
+/// List {
+/// Item { Link(to: "/article1") { "Article 1" } }
+/// Item { Link(to: "/article2") { "Article 2" } }
+/// }
+/// }
+/// ```
+public final class Aside: Element {
+ /// Creates a new HTML aside element.
+ ///
+ /// - Parameters:
+ /// - id: Unique identifier for the HTML element, useful for styling and scripting.
+ /// - classes: An array of CSS classnames for styling the aside container.
+ /// - role: ARIA role of the element for accessibility and screen readers.
+ /// - label: ARIA label to describe the element's purpose (e.g., "Related Content").
+ /// - data: Dictionary of `data-*` attributes for storing custom data related to the aside.
+ /// - content: Closure providing aside content, such as related links, footnotes, or supplementary information.
+ ///
+ /// ## Example
+ /// ```swift
+ /// Aside(id: "glossary", classes: ["note", "bordered"], label: "Term Definition") {
+ /// Heading(.headline) { "Definition" }
+ /// Text { "A detailed explanation of the term..." }
+ /// }
+ /// ```
+ public init(
+ id: String? = nil,
+ classes: [String]? = nil,
+ role: AriaRole? = nil,
+ label: String? = nil,
+ data: [String: String]? = nil,
+ @HTMLBuilder content: @escaping () -> [any HTML] = { [] }
+ ) {
+ super.init(
+ tag: "aside",
+ id: id,
+ classes: classes,
+ role: role,
+ label: label,
+ data: data,
+ content: content
+ )
+ }
+}
diff --git a/Sources/WebUI/Elements/Structure/Layout/Footer.swift b/Sources/WebUI/Elements/Structure/Layout/Footer.swift
new file mode 100644
index 00000000..097f61c7
--- /dev/null
+++ b/Sources/WebUI/Elements/Structure/Layout/Footer.swift
@@ -0,0 +1,54 @@
+/// Generates an HTML footer element for page or section footers.
+///
+/// The `Footer` element represents a footer for its nearest sectioning content or sectioning root
+/// element. A footer typically contains information about the author, copyright data, related links,
+/// legal information, and other metadata that appears at the end of a document or section.
+///
+/// ## Example
+/// ```swift
+/// Footer {
+/// Text { "© 2023 My Company. All rights reserved." }
+/// Link(to: "/privacy") { "Privacy Policy" }
+/// Link(to: "/terms") { "Terms of Service" }
+/// }
+/// ```
+public final class Footer: Element {
+ /// Creates a new HTML footer element.
+ ///
+ /// - Parameters:
+ /// - id: Unique identifier for the HTML element, useful for styling and scripting.
+ /// - classes: An array of CSS classnames for styling the footer.
+ /// - role: ARIA role of the element for accessibility and screen readers.
+ /// - label: ARIA label to describe the element's purpose (e.g., "Page Footer").
+ /// - data: Dictionary of `data-*` attributes for storing custom data related to the footer.
+ /// - content: Closure providing footer content, such as copyright notices, contact information, and secondary navigation.
+ ///
+ /// ## Example
+ /// ```swift
+ /// Footer(id: "site-footer", classes: ["footer", "bg-dark"]) {
+ /// Stack(classes: ["footer-links"]) {
+ /// Link(to: "/about") { "About" }
+ /// Link(to: "/contact") { "Contact" }
+ /// }
+ /// Text { "© \(Date().formattedYear()) My Company" }
+ /// }
+ /// ```
+ public init(
+ id: String? = nil,
+ classes: [String]? = nil,
+ role: AriaRole? = nil,
+ label: String? = nil,
+ data: [String: String]? = nil,
+ @HTMLBuilder content: @escaping () -> [any HTML] = { [] }
+ ) {
+ super.init(
+ tag: "footer",
+ id: id,
+ classes: classes,
+ role: role,
+ label: label,
+ data: data,
+ content: content
+ )
+ }
+}
diff --git a/Sources/WebUI/Elements/Structure/Layout/Header.swift b/Sources/WebUI/Elements/Structure/Layout/Header.swift
new file mode 100644
index 00000000..67657358
--- /dev/null
+++ b/Sources/WebUI/Elements/Structure/Layout/Header.swift
@@ -0,0 +1,52 @@
+/// Generates an HTML header element for page or section headers.
+///
+/// The `Header` element represents a container for introductory content or a set of navigational links
+/// at the beginning of a section or page. Typically contains elements like site logos, navigation menus,
+/// and search forms.
+///
+/// ## Example
+/// ```swift
+/// Header {
+/// Heading(.largeTitle) { "Site Title" }
+/// Navigation {
+/// Link(to: "/home") { "Home" }
+/// Link(to: "/about") { "About" }
+/// }
+/// }
+/// ```
+public final class Header: Element {
+ /// Creates a new HTML header element.
+ ///
+ /// - Parameters:
+ /// - id: Unique identifier for the HTML element, useful for styling and scripting.
+ /// - classes: An array of CSS classnames for styling the header.
+ /// - role: ARIA role of the element for accessibility and screen readers.
+ /// - label: ARIA label to describe the element for screen readers.
+ /// - data: Dictionary of `data-*` attributes for storing custom data related to the header.
+ /// - content: Closure providing header content like headings, navigation, and logos.
+ ///
+ /// ## Example
+ /// ```swift
+ /// Header(id: "main-header", classes: ["site-header", "sticky"]) {
+ /// Heading(.largeTitle) { "My Website" }
+ /// }
+ /// ```
+ public init(
+ id: String? = nil,
+ classes: [String]? = nil,
+ role: AriaRole? = nil,
+ label: String? = nil,
+ data: [String: String]? = nil,
+ @HTMLBuilder content: @escaping () -> [any HTML] = { [] }
+ ) {
+ super.init(
+ tag: "header",
+ id: id,
+ classes: classes,
+ role: role,
+ label: label,
+ data: data,
+ content: content
+ )
+ }
+}
diff --git a/Sources/WebUI/Elements/Structure/Layout/MainElement.swift b/Sources/WebUI/Elements/Structure/Layout/MainElement.swift
new file mode 100644
index 00000000..993c3f4e
--- /dev/null
+++ b/Sources/WebUI/Elements/Structure/Layout/MainElement.swift
@@ -0,0 +1,56 @@
+/// Generates an HTML main element for the primary content of a page.
+///
+/// The `Main` element represents the dominant content of the document body. It contains content
+/// that is directly related to or expands upon the central topic of the document. Each page
+/// should have only one `main` element, which helps assistive technologies navigate to the
+/// primary content.
+///
+/// ## Example
+/// ```swift
+/// Main {
+/// Heading(.largeTitle) { "Welcome to Our Website" }
+/// Text { "This is the main content of our heomepage." }
+/// Article {
+/// // Article content
+/// }
+/// }
+/// ```
+public final class Main: Element {
+ /// Creates a new HTML main element.
+ ///
+ /// - Parameters:
+ /// - id: Unique identifier for the HTML element, useful for styling and scripting.
+ /// - classes: An array of CSS classnames for styling the main content area.
+ /// - role: ARIA role of the element for accessibility and screen readers.
+ /// - label: ARIA label to describe the element's purpose (e.g., "Main Content").
+ /// - data: Dictionary of `data-*` attributes for storing custom data related to the main content.
+ /// - content: Closure providing the primary content of the page, typically including articles, sections, and other content elements.
+ ///
+ /// ## Example
+ /// ```swift
+ /// Main(id: "content", classes: ["container"]) {
+ /// Section {
+ /// Heading(.largeTitle) { "About Us" }
+ /// Text { "Learn more about our company history..." }
+ /// }
+ /// }
+ /// ```
+ public init(
+ id: String? = nil,
+ classes: [String]? = nil,
+ role: AriaRole? = nil,
+ label: String? = nil,
+ data: [String: String]? = nil,
+ @HTMLBuilder content: @escaping () -> [any HTML] = { [] }
+ ) {
+ super.init(
+ tag: "main",
+ id: id,
+ classes: classes,
+ role: role,
+ label: label,
+ data: data,
+ content: content
+ )
+ }
+}
diff --git a/Sources/WebUI/Elements/Structure/Layout/Navigation.swift b/Sources/WebUI/Elements/Structure/Layout/Navigation.swift
new file mode 100644
index 00000000..8dd8c18b
--- /dev/null
+++ b/Sources/WebUI/Elements/Structure/Layout/Navigation.swift
@@ -0,0 +1,53 @@
+/// Generates an HTML navigation element for site navigation.
+///
+/// The `Navigation` element represents a section of a page intended to contain navigation
+/// links to other pages or parts within the current page. It helps screen readers and
+/// other assistive technologies identify the main navigation structure of the website.
+///
+/// ## Example
+/// ```swift
+/// Navigation(classes: ["main-nav"]) {
+/// List {
+/// Item { Link(to: "/") { "Home" } }
+/// Item { Link(to: "/products") { "Products" } }
+/// Item { Link(to: "/contact") { "Contact" } }
+/// }
+/// }
+/// ```
+public final class Navigation: Element {
+ /// Creates a new HTML navigation element.
+ ///
+ /// - Parameters:
+ /// - id: Unique identifier for the HTML element, useful for styling and scripting.
+ /// - classes: An array of CSS classnames for styling the navigation container.
+ /// - role: ARIA role of the element for accessibility and screen readers.
+ /// - label: ARIA label to describe the element's purpose (e.g., "Main Navigation").
+ /// - data: Dictionary of `data-*` attributes for storing custom data related to navigation.
+ /// - content: Closure providing navigation content, typically links or lists of links.
+ ///
+ /// ## Example
+ /// ```swift
+ /// Navigation(id: "main-nav", label: "Main Navigation") {
+ /// Link(to: "/home") { "Home" }
+ /// Link(to: "/about") { "About Us" }
+ /// }
+ /// ```
+ public init(
+ id: String? = nil,
+ classes: [String]? = nil,
+ role: AriaRole? = nil,
+ label: String? = nil,
+ data: [String: String]? = nil,
+ @HTMLBuilder content: @escaping () -> [any HTML] = { [] }
+ ) {
+ super.init(
+ tag: "nav",
+ id: id,
+ classes: classes,
+ role: role,
+ label: label,
+ data: data,
+ content: content
+ )
+ }
+}
diff --git a/Sources/WebUI/Elements/Structure/Layout/Section.swift b/Sources/WebUI/Elements/Structure/Layout/Section.swift
new file mode 100644
index 00000000..3071aefe
--- /dev/null
+++ b/Sources/WebUI/Elements/Structure/Layout/Section.swift
@@ -0,0 +1,54 @@
+/// Generates an HTML section element for thematic content grouping.
+///
+/// Defines a thematic grouping of content, such as a chapter, tab panel, or any content
+/// that forms a distinct section of a document. Sections typically have their own heading
+/// and represent a logical grouping of related content.
+///
+/// ## Example
+/// ```swift
+/// Section(id: "features") {
+/// Heading(.title) { "Key Features" }
+/// List {
+/// Item { "Simple API" }
+/// Item { "Type-safe HTML generation" }
+/// Item { "Responsive design" }
+/// }
+/// }
+/// ```
+public final class Section: Element {
+ /// Creates a new HTML section element for thematic content grouping.
+ ///
+ /// - Parameters:
+ /// - id: Unique identifier for the HTML element, useful for navigation and linking.
+ /// - classes: An array of CSS classnames for styling the section.
+ /// - role: ARIA role of the element for accessibility and screen readers.
+ /// - label: ARIA label to describe the element's purpose for screen readers.
+ /// - data: Dictionary of `data-*` attributes for storing custom data related to the section.
+ /// - content: Closure providing section content such as headings, paragraphs, and other elements.
+ ///
+ /// ## Example
+ /// ```swift
+ /// Section(id: "about", classes: ["content-section"]) {
+ /// Heading(.title) { "About Us" }
+ /// Text { "Our company was founded in 2020..." }
+ /// }
+ /// ```
+ public init(
+ id: String? = nil,
+ classes: [String]? = nil,
+ role: AriaRole? = nil,
+ label: String? = nil,
+ data: [String: String]? = nil,
+ @HTMLBuilder content: @escaping () -> [any HTML] = { [] }
+ ) {
+ super.init(
+ tag: "section",
+ id: id,
+ classes: classes,
+ role: role,
+ label: label,
+ data: data,
+ content: content
+ )
+ }
+}
diff --git a/Sources/WebUI/Elements/Structure/List/Item.swift b/Sources/WebUI/Elements/Structure/List/Item.swift
new file mode 100644
index 00000000..fa46c8f1
--- /dev/null
+++ b/Sources/WebUI/Elements/Structure/List/Item.swift
@@ -0,0 +1,50 @@
+/// Generates an HTML list item element (``).
+///
+/// `Item` elements should be used as children of a `List` element to represent
+/// individual entries in a list. Each item can contain any HTML content.
+///
+/// ## Example
+/// ```swift
+/// Item {
+/// Text { "This is a list item with " }
+/// Strong { "bold text" }
+/// }
+/// // Renders: This is a list item with bold text
+/// ```
+public final class Item: Element {
+ /// Creates a new HTML list item element.
+ ///
+ /// - Parameters:
+ /// - id: Unique identifier for the HTML element.
+ /// - classes: An array of CSS classnames for styling the list item.
+ /// - role: ARIA role of the element for accessibility.
+ /// - label: ARIA label to describe the element for screen readers.
+ /// - data: Dictionary of `data-*` attributes for storing custom data.
+ /// - content: Closure providing the list item's content (text or other HTML elements).
+ ///
+ /// ## Example
+ /// ```swift
+ /// Item(classes: ["completed"], data: ["task-id": "123"]) {
+ /// "Complete documentation"
+ /// }
+ /// // Renders: Complete documentation
+ /// ```
+ public init(
+ id: String? = nil,
+ classes: [String]? = nil,
+ role: AriaRole? = nil,
+ label: String? = nil,
+ data: [String: String]? = nil,
+ @HTMLBuilder content: @escaping () -> [any HTML] = { [] }
+ ) {
+ super.init(
+ tag: "li",
+ id: id,
+ classes: classes,
+ role: role,
+ label: label,
+ data: data,
+ content: content
+ )
+ }
+}
diff --git a/Sources/WebUI/Elements/Structure/List/List.swift b/Sources/WebUI/Elements/Structure/List/List.swift
new file mode 100644
index 00000000..7629b87d
--- /dev/null
+++ b/Sources/WebUI/Elements/Structure/List/List.swift
@@ -0,0 +1,62 @@
+/// Generates HTML list elements (`` or ``).
+///
+/// `List` can be rendered as an unordered list with bullet points when sequence is unimportant,
+/// or as an ordered list with numbered markings when sequence matters.
+///
+/// - Note: Use `Item` elements as children of a `List` to create list items.
+///
+/// ## Example
+/// ```swift
+/// List(type: .ordered) {
+/// Item { "First item" }
+/// Item { "Second item" }
+/// Item { "Third item" }
+/// }
+/// // Renders: First item Second item Third item
+/// ```
+public final class List: Element {
+ let type: ListType
+ let style: ListStyle
+
+ /// Creates a new HTML list element (`` or ``).
+ ///
+ /// - Parameters:
+ /// - type: List type (ordered or unordered), defaults to unordered.
+ /// - style: List style (disc, circle, or square), defaults to none.
+ /// - id: Unique identifier for the HTML element.
+ /// - classes: An array of CSS classnames for styling the list.
+ /// - role: ARIA role of the element for accessibility.
+ /// - label: ARIA label to describe the element for screen readers.
+ /// - data: Dictionary of `data-*` attributes for storing custom data.
+ /// - content: Closure providing list items, typically `Item` elements.
+ ///
+ /// ## Example
+ /// ```swift
+ /// List(type: .unordered, classes: ["checklist"]) {
+ /// Item { "Buy groceries" }
+ /// Item { "Clean house" }
+ /// }
+ /// ```
+ public init(
+ type: ListType = .unordered,
+ style: ListStyle = .none,
+ id: String? = nil,
+ classes: [String]? = nil,
+ role: AriaRole? = nil,
+ label: String? = nil,
+ data: [String: String]? = nil,
+ @HTMLBuilder content: @escaping () -> [any HTML] = { [] }
+ ) {
+ self.type = type
+ self.style = style
+ super.init(
+ tag: type.rawValue,
+ id: id,
+ classes: (classes ?? []) + (style != .none ? ["list-\(style.rawValue)"] : []),
+ role: role,
+ label: label,
+ data: data,
+ content: content
+ )
+ }
+}
diff --git a/Sources/WebUI/Elements/Structure/List/ListStyle.swift b/Sources/WebUI/Elements/Structure/List/ListStyle.swift
new file mode 100644
index 00000000..13e93b83
--- /dev/null
+++ b/Sources/WebUI/Elements/Structure/List/ListStyle.swift
@@ -0,0 +1,17 @@
+/// Defines styles for HTML list elements.
+///
+/// HTML supports various styles for list elements, such as disc, circle, or square.
+/// This enum provides a type-safe way to specify which style to use.
+public enum ListStyle: String {
+ /// Creates a list with no bullets or numbers.
+ case none = ""
+
+ /// Creates a list with bullets shaped like discs.
+ case disc
+
+ /// Creates a list with bullets shaped like circles.
+ case circle
+
+ /// Creates a list with bullets shaped like squares.
+ case square = "[square]"
+}
diff --git a/Sources/WebUI/Elements/Structure/List/ListType.swift b/Sources/WebUI/Elements/Structure/List/ListType.swift
new file mode 100644
index 00000000..ffae93e7
--- /dev/null
+++ b/Sources/WebUI/Elements/Structure/List/ListType.swift
@@ -0,0 +1,15 @@
+/// Defines types of HTML list elements.
+///
+/// HTML supports two main types of lists: ordered (numbered) lists and unordered (bulleted) lists.
+/// This enum provides a type-safe way to specify which list type to create.
+public enum ListType: String {
+ /// Creates an ordered (numbered) list using the `` tag.
+ ///
+ /// Use for sequential, prioritized, or step-by-step items.
+ case ordered = "ol"
+
+ /// Creates an unordered (bulleted) list using the `` tag.
+ ///
+ /// Use for items where the sequence doesn't matter.
+ case unordered = "ul"
+}
diff --git a/Sources/WebUI/Elements/Structure/Stack.swift b/Sources/WebUI/Elements/Structure/Stack.swift
new file mode 100644
index 00000000..3fb5819e
--- /dev/null
+++ b/Sources/WebUI/Elements/Structure/Stack.swift
@@ -0,0 +1,58 @@
+/// Generates an HTML div element for generic content grouping.
+///
+/// The `Stack` element (which renders as a div) groups elements for styling or layout
+/// without conveying any specific semantic meaning. It's a versatile container used
+/// for creating layout structures, applying styles to groups, or providing hooks for
+/// JavaScript functionality.
+///
+/// - Note: Use semantic elements like `Article`, `Section`, or `Aside` when possible,
+/// and reserve `Stack` for purely presentational grouping.
+///
+/// - Note: Utilize the `flex` and `grid` modifiers for layout controls. This is different to
+/// SwiftUI's `HStack` and `VStack` because responsive layouts in CSS require a different approach.
+///
+/// ## Example
+/// ```swift
+/// Stack(classes: ["flex-container"]) {
+/// Stack(classes: ["card"]) { "Card 1 content" }
+/// Stack(classes: ["card"]) { "Card 2 content" }
+/// }
+/// ```
+public final class Stack: Element {
+ /// Creates a new HTML div element for generic content grouping.
+ ///
+ /// - Parameters:
+ /// - id: Unique identifier for the HTML element, useful for styling and scripting.
+ /// - classes: An array of CSS classnames for styling the div container.
+ /// - role: ARIA role of the element for accessibility and screen readers.
+ /// - label: ARIA label to describe the element's purpose for screen readers.
+ /// - data: Dictionary of `data-*` attributes for storing custom data related to the container.
+ /// - content: Closure providing the container's content elements.
+ ///
+ /// ## Example
+ /// ```swift
+ /// Stack(id: "user-profile", classes: ["card", "shadow"], data: ["user-id": "123"]) {
+ /// Image(source: "/avatar.jpg", description: "User Avatar")
+ /// Heading(.headline) { "Jane Doe" }
+ /// Text { "Software Engineer" }
+ /// }
+ /// ```
+ public init(
+ id: String? = nil,
+ classes: [String]? = nil,
+ role: AriaRole? = nil,
+ label: String? = nil,
+ data: [String: String]? = nil,
+ @HTMLBuilder content: @escaping () -> [any HTML] = { [] }
+ ) {
+ super.init(
+ tag: "div",
+ id: id,
+ classes: classes,
+ role: role,
+ label: label,
+ data: data,
+ content: content
+ )
+ }
+}
diff --git a/Sources/WebUI/Elements/Base/Abbreviation.swift b/Sources/WebUI/Elements/Text/Abbreviation.swift
similarity index 66%
rename from Sources/WebUI/Elements/Base/Abbreviation.swift
rename to Sources/WebUI/Elements/Text/Abbreviation.swift
index b8879c86..1379feca 100644
--- a/Sources/WebUI/Elements/Base/Abbreviation.swift
+++ b/Sources/WebUI/Elements/Text/Abbreviation.swift
@@ -1,11 +1,11 @@
import Foundation
-/// Generates an HTML abbreviation element (``) for displaying abbreviations or acronyms.
+/// Creates HTML abbreviation elements for displaying abbreviations or acronyms.
///
-/// The abbreviation element allows you to define the full term for an abbreviation or acronym,
-/// which helps with accessibility and provides a visual indication to users (typically shown
-/// with a dotted underline). When users hover over the abbreviation, browsers typically
-/// display the full term as a tooltip.
+/// Represents the full form of an abbreviation or acronym, enhancing accessibility and usability.
+/// Abbreviation elements provide a visual indication to users (typically shown with a dotted underline)
+/// and display the full term as a tooltip when users hover over them. This improves content comprehension
+/// and assists screen reader users.
///
/// ## Example
/// ```swift
@@ -19,11 +19,11 @@ public final class Abbreviation: Element {
///
/// - Parameters:
/// - title: The full term or explanation of the abbreviation.
- /// - id: Unique identifier for the HTML element.
+ /// - id: Unique identifier for the HTML element, useful for JavaScript interaction and styling.
/// - classes: An array of CSS classnames for styling the abbreviation.
- /// - role: ARIA role of the element for accessibility.
- /// - label: ARIA label to describe the element for screen readers.
- /// - data: Dictionary of `data-*` attributes for storing element-specific data.
+ /// - role: ARIA role of the element for accessibility, enhancing screen reader interpretation.
+ /// - label: ARIA label to describe the element for accessibility when context isn't sufficient.
+ /// - data: Dictionary of `data-*` attributes for storing custom data relevant to the abbreviation.
/// - content: Closure providing the content (typically the abbreviated term).
///
/// ## Example
diff --git a/Sources/WebUI/Elements/Text/Code.swift b/Sources/WebUI/Elements/Text/Code.swift
new file mode 100644
index 00000000..7e31970b
--- /dev/null
+++ b/Sources/WebUI/Elements/Text/Code.swift
@@ -0,0 +1,42 @@
+import Foundation
+
+/// Creates HTML code elements for displaying code snippets.
+///
+/// Represents a fragment of computer code, typically displayed in a monospace font.
+/// Code elements are useful for inline code references, syntax highlighting, and technical documentation.
+public final class Code: Element {
+ /// Creates a new HTML code element.
+ ///
+ /// - Parameters:
+ /// - id: Unique identifier for the HTML element, useful for JavaScript interaction and styling.
+ /// - classes: An array of CSS classnames for styling the code element.
+ /// - role: ARIA role of the element for accessibility, enhancing screen reader interpretation.
+ /// - label: ARIA label to describe the element for accessibility when context isn't sufficient.
+ /// - data: Dictionary of `data-*` attributes for storing custom data relevant to the code element.
+ /// - content: Closure providing code content.
+ ///
+ /// ## Example
+ /// ```swift
+ /// Code {
+ /// "let x = 42"
+ /// }
+ /// ```
+ public init(
+ id: String? = nil,
+ classes: [String]? = nil,
+ role: AriaRole? = nil,
+ label: String? = nil,
+ data: [String: String]? = nil,
+ @HTMLBuilder content: @escaping () -> [any HTML] = { [] }
+ ) {
+ super.init(
+ tag: "code",
+ id: id,
+ classes: classes,
+ role: role,
+ label: label,
+ data: data,
+ content: content
+ )
+ }
+}
diff --git a/Sources/WebUI/Elements/Text/Emphasis.swift b/Sources/WebUI/Elements/Text/Emphasis.swift
new file mode 100644
index 00000000..0e490270
--- /dev/null
+++ b/Sources/WebUI/Elements/Text/Emphasis.swift
@@ -0,0 +1,43 @@
+import Foundation
+
+/// Creates HTML emphasis elements for highlighting important text.
+///
+/// Represents emphasized text with semantic importance, typically displayed in italics.
+/// Emphasis elements are used to draw attention to text within another body of text
+/// and provide semantic meaning that enhances accessibility and comprehension.
+public final class Emphasis: Element {
+ /// Creates a new HTML emphasis element.
+ ///
+ /// - Parameters:
+ /// - id: Unique identifier for the HTML element, useful for JavaScript interaction and styling.
+ /// - classes: An array of CSS classnames for styling the emphasized text.
+ /// - role: ARIA role of the element for accessibility, enhancing screen reader interpretation.
+ /// - label: ARIA label to describe the element for accessibility when context isn't sufficient.
+ /// - data: Dictionary of `data-*` attributes for storing custom data relevant to the element.
+ /// - content: Closure providing the text content to be emphasized.
+ ///
+ /// ## Example
+ /// ```swift
+ /// Emphasis {
+ /// "This text is emphasized"
+ /// }
+ /// ```
+ public init(
+ id: String? = nil,
+ classes: [String]? = nil,
+ role: AriaRole? = nil,
+ label: String? = nil,
+ data: [String: String]? = nil,
+ @HTMLBuilder content: @escaping () -> [any HTML] = { [] }
+ ) {
+ super.init(
+ tag: "em",
+ id: id,
+ classes: classes,
+ role: role,
+ label: label,
+ data: data,
+ content: content
+ )
+ }
+}
diff --git a/Sources/WebUI/Elements/Text/Heading/Heading.swift b/Sources/WebUI/Elements/Text/Heading/Heading.swift
new file mode 100644
index 00000000..cb97b4aa
--- /dev/null
+++ b/Sources/WebUI/Elements/Text/Heading/Heading.swift
@@ -0,0 +1,45 @@
+import Foundation
+
+/// Creates HTML heading elements from `` to ``.
+///
+/// Represents section headings of different levels, with `` being the highest (most important)
+/// and `` the lowest. Headings provide document structure and are essential for accessibility,
+/// SEO, and reader comprehension.
+public final class Heading: Element {
+ /// Creates a new HTML heading element.
+ ///
+ /// - Parameters:
+ /// - level: Heading level (.largeTitle, .title, .headline, .subheadline, .body, or .footnote).
+ /// - id: Unique identifier for the HTML element, useful for JavaScript interaction and styling.
+ /// - classes: An array of CSS classnames for styling the heading.
+ /// - role: ARIA role of the element for accessibility, enhancing screen reader interpretation.
+ /// - label: ARIA label to describe the element for accessibility when context isn't sufficient.
+ /// - data: Dictionary of `data-*` attributes for storing custom data relevant to the heading.
+ /// - content: Closure providing heading content.
+ ///
+ /// ## Example
+ /// ```swift
+ /// Heading(.title) {
+ /// "Section Title"
+ /// }
+ /// ```
+ public init(
+ _ level: HeadingLevel,
+ id: String? = nil,
+ classes: [String]? = nil,
+ role: AriaRole? = nil,
+ label: String? = nil,
+ data: [String: String]? = nil,
+ @HTMLBuilder content: @escaping () -> [any HTML] = { [] }
+ ) {
+ super.init(
+ tag: level.rawValue,
+ id: id,
+ classes: classes,
+ role: role,
+ label: label,
+ data: data,
+ content: content
+ )
+ }
+}
diff --git a/Sources/WebUI/Elements/Text/Heading/HeadingLevel.swift b/Sources/WebUI/Elements/Text/Heading/HeadingLevel.swift
new file mode 100644
index 00000000..dea9d150
--- /dev/null
+++ b/Sources/WebUI/Elements/Text/Heading/HeadingLevel.swift
@@ -0,0 +1,27 @@
+/// Defines semantic levels for HTML heading tags from h1 to h6.
+///
+/// HTML headings provide document structure and establish content hierarchy.
+/// Each level has a specific semantic meaning, with h1 being the most important
+/// and h6 the least. Proper use of heading levels improves accessibility,
+/// SEO, and overall document organization.
+///
+/// ## Example
+/// ```swift
+/// Heading(.largeTitle) {
+/// "Main Page Title"
+/// }
+/// ```
+public enum HeadingLevel: String {
+ /// Large title, most prominent heading (h1).
+ case largeTitle = "h1"
+ /// Title, second most prominent heading (h2).
+ case title = "h2"
+ /// Headline, third most prominent heading (h3).
+ case headline = "h3"
+ /// Subheadline, fourth most prominent heading (h4).
+ case subheadline = "h4"
+ /// Body, fifth most prominent heading (h5).
+ case body = "h5"
+ /// Footnote, least prominent heading (h6).
+ case footnote = "h6"
+}
diff --git a/Sources/WebUI/Elements/Text/Link.swift b/Sources/WebUI/Elements/Text/Link.swift
new file mode 100644
index 00000000..42a264bc
--- /dev/null
+++ b/Sources/WebUI/Elements/Text/Link.swift
@@ -0,0 +1,63 @@
+import Foundation
+
+/// Creates HTML anchor elements for linking to other locations.
+///
+/// Represents a hyperlink that connects to another web page, file, location within the same page,
+/// email address, or any other URL. Links are fundamental interactive elements that enable
+/// navigation throughout a website and to external resources.
+public final class Link: Element {
+ private let href: String
+ private let newTab: Bool?
+
+ /// Creates a new HTML anchor link.
+ ///
+ /// - Parameters:
+ /// - destination: URL or path the link points to.
+ /// - newTab: Opens in a new tab if true, optional.
+ /// - id: Unique identifier for the HTML element, useful for JavaScript interaction and styling.
+ /// - classes: An array of CSS classnames for styling the link.
+ /// - role: ARIA role of the element for accessibility, enhancing screen reader interpretation.
+ /// - label: ARIA label to describe the element for accessibility when link text isn't sufficient.
+ /// - data: Dictionary of `data-*` attributes for storing custom data relevant to the link.
+ /// - content: Closure providing link content (text or other HTML elements).
+ ///
+ /// ## Example
+ /// ```swift
+ /// Link(to: "https://example.com", newTab: true) {
+ /// "Visit Example Website"
+ /// }
+ /// ```
+ public init(
+ to destination: String,
+ newTab: Bool? = nil,
+ id: String? = nil,
+ classes: [String]? = nil,
+ role: AriaRole? = nil,
+ label: String? = nil,
+ data: [String: String]? = nil,
+ @HTMLBuilder content: @escaping () -> [any HTML] = { [] }
+ ) {
+ self.href = destination
+ self.newTab = newTab
+
+ var attributes = [Attribute.string("href", destination)].compactMap { $0 }
+
+ if newTab == true {
+ attributes.append(contentsOf: [
+ "target=\"_blank\"",
+ "rel=\"noreferrer\"",
+ ])
+ }
+
+ super.init(
+ tag: "a",
+ id: id,
+ classes: classes,
+ role: role,
+ label: label,
+ data: data,
+ customAttributes: attributes.isEmpty ? nil : attributes,
+ content: content
+ )
+ }
+}
diff --git a/Sources/WebUI/Elements/Text/Preformatted.swift b/Sources/WebUI/Elements/Text/Preformatted.swift
new file mode 100644
index 00000000..40b89649
--- /dev/null
+++ b/Sources/WebUI/Elements/Text/Preformatted.swift
@@ -0,0 +1,63 @@
+import Foundation
+
+/// Creates HTML preformatted text elements.
+///
+/// Represents text that should be displayed exactly as written, preserving whitespace, line breaks,
+/// and formatting. Commonly used for displaying code snippets, computer output, ASCII art, or any text
+/// where spacing and formatting are significant. The `` element renders text in a monospaced font
+/// and maintains all whitespace characters.
+///
+/// ## Example
+/// ```swift
+/// Preformatted {
+/// """
+/// function greet() {
+/// console.log("Hello, world!");
+/// }
+/// """
+/// }
+/// ```
+public final class Preformatted: Element {
+ /// Creates a new HTML preformatted element.
+ ///
+ /// - Parameters:
+ /// - id: Unique identifier for the HTML element, useful for JavaScript interaction and styling.
+ /// - classes: An array of CSS classnames for styling the preformatted text.
+ /// - role: ARIA role of the element for accessibility, enhancing screen reader interpretation.
+ /// - label: ARIA label to describe the element for accessibility when context isn't sufficient.
+ /// - data: Dictionary of `data-*` attributes for storing custom data relevant to the element.
+ /// - content: Closure providing the preformatted content to be displayed.
+ ///
+ /// ## Example
+ /// ```swift
+ /// Preformatted(
+ /// classes: ["code-block", "language-swift"],
+ /// role: .code
+ /// ) {
+ /// """
+ /// struct Person {
+ /// let name: String
+ /// let age: Int
+ /// }
+ /// """
+ /// }
+ /// ```
+ public init(
+ id: String? = nil,
+ classes: [String]? = nil,
+ role: AriaRole? = nil,
+ label: String? = nil,
+ data: [String: String]? = nil,
+ @HTMLBuilder content: @escaping () -> [any HTML] = { [] }
+ ) {
+ super.init(
+ tag: "pre",
+ id: id,
+ classes: classes,
+ role: role,
+ label: label,
+ data: data,
+ content: content
+ )
+ }
+}
diff --git a/Sources/WebUI/Elements/Text/Strong.swift b/Sources/WebUI/Elements/Text/Strong.swift
new file mode 100644
index 00000000..997a9875
--- /dev/null
+++ b/Sources/WebUI/Elements/Text/Strong.swift
@@ -0,0 +1,51 @@
+import Foundation
+
+/// Creates HTML strong emphasis elements.
+///
+/// Represents text with strong emphasis or importance, typically displayed in bold by browsers.
+/// The `` element indicates content with high importance, seriousness, or urgency.
+/// It differs from `` (bold) which stylistically offsets text without implying importance,
+/// and from `` which indicates stress emphasis.
+///
+/// ## Example
+/// ```swift
+/// Strong { "Warning: This action cannot be undone!" }
+/// ```
+public final class Strong: Element {
+ /// Creates a new HTML strong element.
+ ///
+ /// - Parameters:
+ /// - id: Unique identifier for the HTML element, useful for JavaScript interaction and styling.
+ /// - classes: An array of CSS classnames for styling the emphasized text.
+ /// - role: ARIA role of the element for accessibility, enhancing screen reader interpretation.
+ /// - label: ARIA label to describe the element for accessibility when context isn't sufficient.
+ /// - data: Dictionary of `data-*` attributes for storing custom data relevant to the element.
+ /// - content: Closure providing the content to be strongly emphasized.
+ ///
+ /// ## Example
+ /// ```swift
+ /// Strong(
+ /// classes: ["alert", "critical"]
+ /// ) {
+ /// "Important security notice"
+ /// }
+ /// ```
+ public init(
+ id: String? = nil,
+ classes: [String]? = nil,
+ role: AriaRole? = nil,
+ label: String? = nil,
+ data: [String: String]? = nil,
+ @HTMLBuilder content: @escaping () -> [any HTML] = { [] }
+ ) {
+ super.init(
+ tag: "strong",
+ id: id,
+ classes: classes,
+ role: role,
+ label: label,
+ data: data,
+ content: content
+ )
+ }
+}
diff --git a/Sources/WebUI/Elements/Base/Style.swift b/Sources/WebUI/Elements/Text/Style.swift
similarity index 100%
rename from Sources/WebUI/Elements/Base/Style.swift
rename to Sources/WebUI/Elements/Text/Style.swift
diff --git a/Sources/WebUI/Elements/Text/Text.swift b/Sources/WebUI/Elements/Text/Text.swift
new file mode 100644
index 00000000..bee69d87
--- /dev/null
+++ b/Sources/WebUI/Elements/Text/Text.swift
@@ -0,0 +1,44 @@
+import Foundation
+
+/// Generates HTML text elements as `` or `` based on content.
+///
+/// Paragraphs are for long form content with multiple sentences and
+/// a `` tag is used for a single sentence of text and grouping inline content.
+public final class Text: Element {
+ /// Creates a new text element.
+ ///
+ /// Uses `` for multiple sentences, `` for one or fewer.
+ ///
+ /// - Parameters:
+ /// - id: Unique identifier for the HTML element.
+ /// - classes: An array of CSS classnames.
+ /// - role: ARIA role of the element for accessibility.
+ /// - label: ARIA label to describe the element.
+ /// - data: Dictionary of `data-*` attributes for element relevant storing data.
+ /// - content: Closure providing text content.
+ public init(
+ id: String? = nil,
+ classes: [String]? = nil,
+ role: AriaRole? = nil,
+ label: String? = nil,
+ data: [String: String]? = nil,
+ @HTMLBuilder content: @escaping () -> [any HTML]
+ ) {
+ let renderedContent = content().map { $0.render() }.joined()
+ let sentenceCount = renderedContent.components(
+ separatedBy: CharacterSet(charactersIn: ".!?")
+ )
+ .filter { !$0.trimmingCharacters(in: .whitespaces).isEmpty }
+ .count
+ let tag = sentenceCount > 1 ? "p" : "span"
+ super.init(
+ tag: tag,
+ id: id,
+ classes: classes,
+ role: role,
+ label: label,
+ data: data,
+ content: content
+ )
+ }
+}
diff --git a/Sources/WebUI/Elements/Text/Time.swift b/Sources/WebUI/Elements/Text/Time.swift
new file mode 100644
index 00000000..057fe028
--- /dev/null
+++ b/Sources/WebUI/Elements/Text/Time.swift
@@ -0,0 +1,63 @@
+import Foundation
+
+/// Creates HTML time elements for representing dates and times.
+///
+/// Represents a specific date, time, or duration in a machine-readable format that benefits both
+/// users and automated processes. The time element improves accessibility, enables precise time
+/// interpretation across different locales, and provides semantic meaning to temporal data.
+/// The required `datetime` attribute contains the machine-readable timestamp while the element's
+/// content displays a human-friendly representation.
+///
+/// ## Example
+/// ```swift
+/// Time(datetime: "2023-12-25T00:00:00Z") { "Christmas Day" }
+/// // Renders: Christmas Day
+/// ```
+public final class Time: Element {
+ private let datetime: String
+
+ /// Creates a new HTML time element.
+ ///
+ /// - Parameters:
+ /// - datetime: The machine-readable timestamp in a valid format (ISO 8601 recommended).
+ /// - id: Unique identifier for the HTML element, useful for JavaScript interaction and styling.
+ /// - classes: An array of CSS classnames for styling the time element.
+ /// - role: ARIA role of the element for accessibility, enhancing screen reader interpretation.
+ /// - label: ARIA label to describe the element for accessibility when context isn't sufficient.
+ /// - data: Dictionary of `data-*` attributes for storing custom data relevant to the time element.
+ /// - content: Closure providing the human-readable representation of the time.
+ ///
+ /// ## Example
+ /// ```swift
+ /// Time(
+ /// datetime: "PT2H30M",
+ /// classes: ["duration"]
+ /// ) {
+ /// "2 hours and 30 minutes"
+ /// }
+ /// ```
+ public init(
+ datetime: String,
+ id: String? = nil,
+ classes: [String]? = nil,
+ role: AriaRole? = nil,
+ label: String? = nil,
+ data: [String: String]? = nil,
+ @HTMLBuilder content: @escaping () -> [any HTML] = { [] }
+ ) {
+ self.datetime = datetime
+ let customAttributes = [
+ Attribute.string("datetime", datetime)
+ ].compactMap { $0 }
+ super.init(
+ tag: "time",
+ id: id,
+ classes: classes,
+ role: role,
+ label: label,
+ data: data,
+ customAttributes: customAttributes.isEmpty ? nil : customAttributes,
+ content: content
+ )
+ }
+}
diff --git a/Sources/WebUI/Styles/Appearance/BackgroundStyleOperation.swift b/Sources/WebUI/Styles/Color/BackgroundStyleOperation.swift
similarity index 100%
rename from Sources/WebUI/Styles/Appearance/BackgroundStyleOperation.swift
rename to Sources/WebUI/Styles/Color/BackgroundStyleOperation.swift
diff --git a/Sources/WebUI/Styles/Base/Utilities.swift b/Sources/WebUI/Styles/Core/Utilities.swift
similarity index 97%
rename from Sources/WebUI/Styles/Base/Utilities.swift
rename to Sources/WebUI/Styles/Core/Utilities.swift
index 24f6d953..18c8caa7 100644
--- a/Sources/WebUI/Styles/Base/Utilities.swift
+++ b/Sources/WebUI/Styles/Core/Utilities.swift
@@ -77,62 +77,62 @@ public enum Modifier: String {
///
/// Use to style the first item in a list or container.
case first
-
+
/// Applies the style to the last child element.
///
/// Use to style the last item in a list or container.
case last
-
+
/// Applies the style when the element is disabled.
///
/// Use to provide visual feedback for disabled form elements and controls.
case disabled
-
+
/// Applies the style when the user prefers reduced motion.
///
/// Use to create alternative animations or transitions for users who prefer reduced motion.
case motionReduce = "motion-reduce"
-
+
/// Applies the style when the element has aria-busy="true".
///
/// Use to style elements that are in a busy or loading state.
case ariaBusy = "aria-busy"
-
+
/// Applies the style when the element has aria-checked="true".
///
/// Use to style elements that represent a checked state, like checkboxes.
case ariaChecked = "aria-checked"
-
+
/// Applies the style when the element has aria-disabled="true".
///
/// Use to style elements that are disabled via ARIA attributes.
case ariaDisabled = "aria-disabled"
-
+
/// Applies the style when the element has aria-expanded="true".
///
/// Use to style elements that can be expanded, like accordions or dropdowns.
case ariaExpanded = "aria-expanded"
-
+
/// Applies the style when the element has aria-hidden="true".
///
/// Use to style elements that are hidden from screen readers.
case ariaHidden = "aria-hidden"
-
+
/// Applies the style when the element has aria-pressed="true".
///
/// Use to style elements that represent a pressed state, like toggle buttons.
case ariaPressed = "aria-pressed"
-
+
/// Applies the style when the element has aria-readonly="true".
///
/// Use to style elements that are in a read-only state.
case ariaReadonly = "aria-readonly"
-
+
/// Applies the style when the element has aria-required="true".
///
/// Use to style elements that are required, like form inputs.
case ariaRequired = "aria-required"
-
+
/// Applies the style when the element has aria-selected="true".
///
/// Use to style elements that are in a selected state, like tabs or menu items.
@@ -292,6 +292,16 @@ public func combineClasses(_ baseClasses: [String], withModifiers modifiers: [Mo
/// .font(color: .white)
/// ```
public enum Color {
+ /// Pure white #ffffff color with optional opacity.
+ ///
+ /// A bright white color with no hue or saturatio
+ case white(opacity: Double? = nil)
+
+ /// Pure black #000000 color with optional opacity.
+ ///
+ /// A deep black color with no hue or saturation.
+ case black(opacity: Double? = nil)
+
/// A slate gray color with varying intensity shades and optional opacity.
///
/// A cool gray with subtle blue undertones.
@@ -509,6 +519,10 @@ public enum Color {
}
switch self {
+ case .white(let opacity):
+ return "white-\(formatOpacity(opacity))"
+ case .black(let opacity):
+ return "black-\(formatOpacity(opacity))"
case .slate(let shade, let opacity):
return "slate-\(shade.rawValue)\(formatOpacity(opacity))"
case .gray(let shade, let opacity):
diff --git a/Sources/WebUI/Styles/Appearance/BorderRadiusStyleOperation.swift b/Sources/WebUI/Styles/Effects/Border/BorderRadiusStyleOperation.swift
similarity index 100%
rename from Sources/WebUI/Styles/Appearance/BorderRadiusStyleOperation.swift
rename to Sources/WebUI/Styles/Effects/Border/BorderRadiusStyleOperation.swift
diff --git a/Sources/WebUI/Styles/Appearance/Border/BorderStyleOperation.swift b/Sources/WebUI/Styles/Effects/Border/BorderStyleOperation.swift
similarity index 100%
rename from Sources/WebUI/Styles/Appearance/Border/BorderStyleOperation.swift
rename to Sources/WebUI/Styles/Effects/Border/BorderStyleOperation.swift
diff --git a/Sources/WebUI/Styles/Appearance/Border/BorderTypes.swift b/Sources/WebUI/Styles/Effects/Border/BorderTypes.swift
similarity index 100%
rename from Sources/WebUI/Styles/Appearance/Border/BorderTypes.swift
rename to Sources/WebUI/Styles/Effects/Border/BorderTypes.swift
diff --git a/Sources/WebUI/Styles/Appearance/OpacityStyleOperation.swift b/Sources/WebUI/Styles/Effects/OpacityStyleOperation.swift
similarity index 100%
rename from Sources/WebUI/Styles/Appearance/OpacityStyleOperation.swift
rename to Sources/WebUI/Styles/Effects/OpacityStyleOperation.swift
diff --git a/Sources/WebUI/Styles/Appearance/OutlineStyleOperation.swift b/Sources/WebUI/Styles/Effects/OutlineStyleOperation.swift
similarity index 97%
rename from Sources/WebUI/Styles/Appearance/OutlineStyleOperation.swift
rename to Sources/WebUI/Styles/Effects/OutlineStyleOperation.swift
index 5d279fc7..6539a36b 100644
--- a/Sources/WebUI/Styles/Appearance/OutlineStyleOperation.swift
+++ b/Sources/WebUI/Styles/Effects/OutlineStyleOperation.swift
@@ -9,16 +9,16 @@ public struct OutlineStyleOperation: StyleOperation, @unchecked Sendable {
public struct Parameters {
/// The outline width
public let width: Int?
-
+
/// The outline style (solid, dashed, etc.)
public let style: BorderStyle?
-
+
/// The outline color
public let color: Color?
-
+
/// The outline offset
public let offset: Int?
-
+
/// Creates parameters for outline styling
///
/// - Parameters:
@@ -37,7 +37,7 @@ public struct OutlineStyleOperation: StyleOperation, @unchecked Sendable {
self.color = color
self.offset = offset
}
-
+
/// Creates parameters from a StyleParameters container
///
/// - Parameter params: The style parameters container
@@ -51,40 +51,40 @@ public struct OutlineStyleOperation: StyleOperation, @unchecked Sendable {
)
}
}
-
+
/// Applies the outline style and returns the appropriate CSS classes
///
/// - Parameter params: The parameters for outline styling
/// - Returns: An array of CSS class names to be applied to elements
public func applyClasses(params: Parameters) -> [String] {
var classes = [String]()
-
+
if let width = params.width {
classes.append("outline-\(width)")
}
-
+
if let style = params.style {
classes.append("outline-\(style.rawValue)")
}
-
+
if let color = params.color {
classes.append("outline-\(color.rawValue)")
}
-
+
if let offset = params.offset {
classes.append("outline-offset-\(offset)")
}
-
+
if classes.isEmpty {
classes.append("outline")
}
-
+
return classes
}
-
+
/// Shared instance for use across the framework
public static let shared = OutlineStyleOperation()
-
+
/// Private initializer to enforce singleton usage
private init() {}
}
@@ -125,7 +125,7 @@ extension Element {
color: color,
offset: offset
)
-
+
return OutlineStyleOperation.shared.applyToElement(
self,
params: params,
@@ -157,7 +157,7 @@ extension ResponsiveBuilder {
color: color,
offset: offset
)
-
+
return OutlineStyleOperation.shared.applyToBuilder(self, params: params)
}
}
@@ -183,6 +183,6 @@ public func outline(
color: color,
offset: offset
)
-
+
return OutlineStyleOperation.shared.asModification(params: params)
-}
\ No newline at end of file
+}
diff --git a/Sources/WebUI/Styles/Appearance/RingStyleOperation.swift b/Sources/WebUI/Styles/Effects/RingStyleOperation.swift
similarity index 96%
rename from Sources/WebUI/Styles/Appearance/RingStyleOperation.swift
rename to Sources/WebUI/Styles/Effects/RingStyleOperation.swift
index 7fe91b7b..1761cc97 100644
--- a/Sources/WebUI/Styles/Appearance/RingStyleOperation.swift
+++ b/Sources/WebUI/Styles/Effects/RingStyleOperation.swift
@@ -77,9 +77,7 @@ extension Element {
/// Adds rings with custom width, style, and color to specified edges of an element.
///
/// - Parameters:
- /// - width: The ring width in pixels.
- /// - edges: One or more edges to apply the ring to. Defaults to `.all`.
- /// - style: The ring style (solid, dashed, etc.).
+ /// - size: The width of the ring.
/// - color: The ring color.
/// - modifiers: Zero or more modifiers (e.g., `.hover`, `.md`) to scope the styles.
/// - Returns: A new element with updated ring classes.
diff --git a/Sources/WebUI/Styles/Appearance/Shadow/ShadowStyleOperation.swift b/Sources/WebUI/Styles/Effects/Shadow/ShadowStyleOperation.swift
similarity index 100%
rename from Sources/WebUI/Styles/Appearance/Shadow/ShadowStyleOperation.swift
rename to Sources/WebUI/Styles/Effects/Shadow/ShadowStyleOperation.swift
diff --git a/Sources/WebUI/Styles/Appearance/Shadow/ShadowTypes.swift b/Sources/WebUI/Styles/Effects/Shadow/ShadowTypes.swift
similarity index 100%
rename from Sources/WebUI/Styles/Appearance/Shadow/ShadowTypes.swift
rename to Sources/WebUI/Styles/Effects/Shadow/ShadowTypes.swift
diff --git a/Sources/WebUI/Styles/Positioning/TransformStyleOperation.swift b/Sources/WebUI/Styles/Effects/TransformStyleOperation.swift
similarity index 100%
rename from Sources/WebUI/Styles/Positioning/TransformStyleOperation.swift
rename to Sources/WebUI/Styles/Effects/TransformStyleOperation.swift
diff --git a/Sources/WebUI/Styles/Positioning/Transition/TransitionStyleOperation.swift b/Sources/WebUI/Styles/Effects/Transition/TransitionStyleOperation.swift
similarity index 100%
rename from Sources/WebUI/Styles/Positioning/Transition/TransitionStyleOperation.swift
rename to Sources/WebUI/Styles/Effects/Transition/TransitionStyleOperation.swift
diff --git a/Sources/WebUI/Styles/Positioning/Transition/TransitionTypes.swift b/Sources/WebUI/Styles/Effects/Transition/TransitionTypes.swift
similarity index 100%
rename from Sources/WebUI/Styles/Positioning/Transition/TransitionTypes.swift
rename to Sources/WebUI/Styles/Effects/Transition/TransitionTypes.swift
diff --git a/Sources/WebUI/Styles/Base/CursorStyleOperation.swift b/Sources/WebUI/Styles/Interactivity/CursorStyleOperation.swift
similarity index 100%
rename from Sources/WebUI/Styles/Base/CursorStyleOperation.swift
rename to Sources/WebUI/Styles/Interactivity/CursorStyleOperation.swift
diff --git a/Sources/WebUI/Styles/Core/InteractionModifiers.swift b/Sources/WebUI/Styles/Interactivity/InteractionModifiers.swift
similarity index 99%
rename from Sources/WebUI/Styles/Core/InteractionModifiers.swift
rename to Sources/WebUI/Styles/Interactivity/InteractionModifiers.swift
index ed1f2f1e..ed268a76 100644
--- a/Sources/WebUI/Styles/Core/InteractionModifiers.swift
+++ b/Sources/WebUI/Styles/Interactivity/InteractionModifiers.swift
@@ -17,7 +17,7 @@ extension ResponsiveBuilder {
applyBreakpoint()
return self
}
-
+
/// Applies styles when the element has keyboard focus.
///
/// - Parameter modifications: A closure containing style modifications.
@@ -29,7 +29,7 @@ extension ResponsiveBuilder {
applyBreakpoint()
return self
}
-
+
/// Applies styles when the element is being actively pressed or clicked.
///
/// - Parameter modifications: A closure containing style modifications.
@@ -41,7 +41,7 @@ extension ResponsiveBuilder {
applyBreakpoint()
return self
}
-
+
/// Applies styles to input placeholders within the element.
///
/// - Parameter modifications: A closure containing style modifications.
@@ -53,7 +53,7 @@ extension ResponsiveBuilder {
applyBreakpoint()
return self
}
-
+
/// Applies styles when dark mode is active.
///
/// - Parameter modifications: A closure containing style modifications.
@@ -65,7 +65,7 @@ extension ResponsiveBuilder {
applyBreakpoint()
return self
}
-
+
/// Applies styles to the first child element.
///
/// - Parameter modifications: A closure containing style modifications.
@@ -77,7 +77,7 @@ extension ResponsiveBuilder {
applyBreakpoint()
return self
}
-
+
/// Applies styles to the last child element.
///
/// - Parameter modifications: A closure containing style modifications.
@@ -89,7 +89,7 @@ extension ResponsiveBuilder {
applyBreakpoint()
return self
}
-
+
/// Applies styles when the element is disabled.
///
/// - Parameter modifications: A closure containing style modifications.
@@ -101,7 +101,7 @@ extension ResponsiveBuilder {
applyBreakpoint()
return self
}
-
+
/// Applies styles when the user prefers reduced motion.
///
/// - Parameter modifications: A closure containing style modifications.
@@ -113,7 +113,7 @@ extension ResponsiveBuilder {
applyBreakpoint()
return self
}
-
+
/// Applies styles when the element has aria-busy="true".
///
/// - Parameter modifications: A closure containing style modifications.
@@ -125,7 +125,7 @@ extension ResponsiveBuilder {
applyBreakpoint()
return self
}
-
+
/// Applies styles when the element has aria-checked="true".
///
/// - Parameter modifications: A closure containing style modifications.
@@ -137,7 +137,7 @@ extension ResponsiveBuilder {
applyBreakpoint()
return self
}
-
+
/// Applies styles when the element has aria-disabled="true".
///
/// - Parameter modifications: A closure containing style modifications.
@@ -149,7 +149,7 @@ extension ResponsiveBuilder {
applyBreakpoint()
return self
}
-
+
/// Applies styles when the element has aria-expanded="true".
///
/// - Parameter modifications: A closure containing style modifications.
@@ -161,7 +161,7 @@ extension ResponsiveBuilder {
applyBreakpoint()
return self
}
-
+
/// Applies styles when the element has aria-hidden="true".
///
/// - Parameter modifications: A closure containing style modifications.
@@ -173,7 +173,7 @@ extension ResponsiveBuilder {
applyBreakpoint()
return self
}
-
+
/// Applies styles when the element has aria-pressed="true".
///
/// - Parameter modifications: A closure containing style modifications.
@@ -185,7 +185,7 @@ extension ResponsiveBuilder {
applyBreakpoint()
return self
}
-
+
/// Applies styles when the element has aria-readonly="true".
///
/// - Parameter modifications: A closure containing style modifications.
@@ -197,7 +197,7 @@ extension ResponsiveBuilder {
applyBreakpoint()
return self
}
-
+
/// Applies styles when the element has aria-required="true".
///
/// - Parameter modifications: A closure containing style modifications.
@@ -209,7 +209,7 @@ extension ResponsiveBuilder {
applyBreakpoint()
return self
}
-
+
/// Applies styles when the element has aria-selected="true".
///
/// - Parameter modifications: A closure containing style modifications.
@@ -367,4 +367,4 @@ public func ariaRequired(@ResponsiveStyleBuilder content: () -> ResponsiveModifi
/// - Returns: A responsive modification for the aria-selected state.
public func ariaSelected(@ResponsiveStyleBuilder content: () -> ResponsiveModification) -> ResponsiveModification {
BreakpointModification(breakpoint: .ariaSelected, styleModification: content())
-}
\ No newline at end of file
+}
diff --git a/Sources/WebUI/Styles/Appearance/Display/DisplayStyleOperation.swift b/Sources/WebUI/Styles/Layout/Display/DisplayStyleOperation.swift
similarity index 100%
rename from Sources/WebUI/Styles/Appearance/Display/DisplayStyleOperation.swift
rename to Sources/WebUI/Styles/Layout/Display/DisplayStyleOperation.swift
diff --git a/Sources/WebUI/Styles/Appearance/Display/DisplayTypes.swift b/Sources/WebUI/Styles/Layout/Display/DisplayTypes.swift
similarity index 100%
rename from Sources/WebUI/Styles/Appearance/Display/DisplayTypes.swift
rename to Sources/WebUI/Styles/Layout/Display/DisplayTypes.swift
diff --git a/Sources/WebUI/Styles/Appearance/Display/FlexStyleOperation.swift b/Sources/WebUI/Styles/Layout/Display/FlexStyleOperation.swift
similarity index 97%
rename from Sources/WebUI/Styles/Appearance/Display/FlexStyleOperation.swift
rename to Sources/WebUI/Styles/Layout/Display/FlexStyleOperation.swift
index f40e74bd..0b0119b3 100644
--- a/Sources/WebUI/Styles/Appearance/Display/FlexStyleOperation.swift
+++ b/Sources/WebUI/Styles/Layout/Display/FlexStyleOperation.swift
@@ -9,16 +9,16 @@ public struct FlexStyleOperation: StyleOperation, @unchecked Sendable {
public struct Parameters {
/// The flex direction (row, column, etc.)
public let direction: FlexDirection?
-
+
/// The justify content property (start, center, between, etc.)
public let justify: FlexJustify?
-
+
/// The align items property (start, center, end, etc.)
public let align: FlexAlign?
-
+
/// The flex grow property
public let grow: FlexGrow?
-
+
/// Creates parameters for flex styling
///
/// - Parameters:
@@ -37,7 +37,7 @@ public struct FlexStyleOperation: StyleOperation, @unchecked Sendable {
self.align = align
self.grow = grow
}
-
+
/// Creates parameters from a StyleParameters container
///
/// - Parameter params: The style parameters container
@@ -51,36 +51,36 @@ public struct FlexStyleOperation: StyleOperation, @unchecked Sendable {
)
}
}
-
+
/// Applies the flex style and returns the appropriate CSS classes
///
/// - Parameter params: The parameters for flex styling
/// - Returns: An array of CSS class names to be applied to elements
public func applyClasses(params: Parameters) -> [String] {
var classes = ["flex"]
-
+
if let direction = params.direction {
classes.append("flex-\(direction.rawValue)")
}
-
+
if let justify = params.justify {
classes.append("justify-\(justify.rawValue)")
}
-
+
if let align = params.align {
classes.append("items-\(align.rawValue)")
}
-
+
if let grow = params.grow {
classes.append("flex-\(grow.rawValue)")
}
-
+
return classes
}
-
+
/// Shared instance for use across the framework
public static let shared = FlexStyleOperation()
-
+
/// Private initializer to enforce singleton usage
private init() {}
}
@@ -89,13 +89,13 @@ public struct FlexStyleOperation: StyleOperation, @unchecked Sendable {
public enum FlexDirection: String {
/// Items are arranged horizontally (left to right)
case row
-
+
/// Items are arranged horizontally in reverse (right to left)
case rowReverse = "row-reverse"
-
+
/// Items are arranged vertically (top to bottom)
case column = "col"
-
+
/// Items are arranged vertically in reverse (bottom to top)
case columnReverse = "col-reverse"
}
@@ -104,19 +104,19 @@ public enum FlexDirection: String {
public enum FlexJustify: String {
/// Items are packed at the start of the container
case start
-
+
/// Items are packed at the end of the container
case end
-
+
/// Items are centered along the line
case center
-
+
/// Items are evenly distributed with equal space between them
case between
-
+
/// Items are evenly distributed with equal space around them
case around
-
+
/// Items are evenly distributed with equal space between and around them
case evenly
}
@@ -125,16 +125,16 @@ public enum FlexJustify: String {
public enum FlexAlign: String {
/// Items are aligned at the start of the cross axis
case start
-
+
/// Items are aligned at the end of the cross axis
case end
-
+
/// Items are centered along the cross axis
case center
-
+
/// Items are stretched to fill the container
case stretch
-
+
/// Items are aligned at the baseline
case baseline
}
@@ -143,19 +143,19 @@ public enum FlexAlign: String {
public enum FlexGrow: String {
/// No growing
case none = "0"
-
+
/// Grow with factor 1
case one = "1"
-
+
/// Grow with factor 2
case two = "2"
-
+
/// Grow with factor 3
case three = "3"
-
+
/// Grow with factor 4
case four = "4"
-
+
/// Grow with factor 5
case five = "5"
}
@@ -196,7 +196,7 @@ extension Element {
align: align,
grow: grow
)
-
+
return FlexStyleOperation.shared.applyToElement(
self,
params: params,
@@ -228,7 +228,7 @@ extension ResponsiveBuilder {
align: align,
grow: grow
)
-
+
return FlexStyleOperation.shared.applyToBuilder(self, params: params)
}
}
@@ -254,6 +254,6 @@ public func flex(
align: align,
grow: grow
)
-
+
return FlexStyleOperation.shared.asModification(params: params)
-}
\ No newline at end of file
+}
diff --git a/Sources/WebUI/Styles/Appearance/Display/GridStyleOperation.swift b/Sources/WebUI/Styles/Layout/Display/GridStyleOperation.swift
similarity index 97%
rename from Sources/WebUI/Styles/Appearance/Display/GridStyleOperation.swift
rename to Sources/WebUI/Styles/Layout/Display/GridStyleOperation.swift
index 10a2d4c4..71bb9090 100644
--- a/Sources/WebUI/Styles/Appearance/Display/GridStyleOperation.swift
+++ b/Sources/WebUI/Styles/Layout/Display/GridStyleOperation.swift
@@ -9,19 +9,19 @@ public struct GridStyleOperation: StyleOperation, @unchecked Sendable {
public struct Parameters {
/// The number of grid columns
public let columns: Int?
-
+
/// The number of grid rows
public let rows: Int?
-
+
/// The grid flow direction
public let flow: GridFlow?
-
+
/// The column span value
public let columnSpan: Int?
-
+
/// The row span value
public let rowSpan: Int?
-
+
/// Creates parameters for grid styling
///
/// - Parameters:
@@ -43,7 +43,7 @@ public struct GridStyleOperation: StyleOperation, @unchecked Sendable {
self.columnSpan = columnSpan
self.rowSpan = rowSpan
}
-
+
/// Creates parameters from a StyleParameters container
///
/// - Parameter params: The style parameters container
@@ -58,40 +58,40 @@ public struct GridStyleOperation: StyleOperation, @unchecked Sendable {
)
}
}
-
+
/// Applies the grid style and returns the appropriate CSS classes
///
/// - Parameter params: The parameters for grid styling
/// - Returns: An array of CSS class names to be applied to elements
public func applyClasses(params: Parameters) -> [String] {
var classes = ["grid"]
-
+
if let columns = params.columns {
classes.append("grid-cols-\(columns)")
}
-
+
if let rows = params.rows {
classes.append("grid-rows-\(rows)")
}
-
+
if let flow = params.flow {
classes.append("grid-flow-\(flow.rawValue)")
}
-
+
if let columnSpan = params.columnSpan {
classes.append("col-span-\(columnSpan)")
}
-
+
if let rowSpan = params.rowSpan {
classes.append("row-span-\(rowSpan)")
}
-
+
return classes
}
-
+
/// Shared instance for use across the framework
public static let shared = GridStyleOperation()
-
+
/// Private initializer to enforce singleton usage
private init() {}
}
@@ -100,13 +100,13 @@ public struct GridStyleOperation: StyleOperation, @unchecked Sendable {
public enum GridFlow: String {
/// Items flow row by row
case row
-
+
/// Items flow column by column
case col
-
+
/// Items flow row by row, dense packing
case rowDense = "row-dense"
-
+
/// Items flow column by column, dense packing
case colDense = "col-dense"
}
@@ -150,7 +150,7 @@ extension Element {
columnSpan: columnSpan,
rowSpan: rowSpan
)
-
+
return GridStyleOperation.shared.applyToElement(
self,
params: params,
@@ -185,7 +185,7 @@ extension ResponsiveBuilder {
columnSpan: columnSpan,
rowSpan: rowSpan
)
-
+
return GridStyleOperation.shared.applyToBuilder(self, params: params)
}
}
@@ -214,6 +214,6 @@ public func grid(
columnSpan: columnSpan,
rowSpan: rowSpan
)
-
+
return GridStyleOperation.shared.asModification(params: params)
-}
\ No newline at end of file
+}
diff --git a/Sources/WebUI/Styles/Appearance/Display/VisibilityStyleOperation.swift b/Sources/WebUI/Styles/Layout/Display/VisibilityStyleOperation.swift
similarity index 98%
rename from Sources/WebUI/Styles/Appearance/Display/VisibilityStyleOperation.swift
rename to Sources/WebUI/Styles/Layout/Display/VisibilityStyleOperation.swift
index 919f01fd..b9b3b08a 100644
--- a/Sources/WebUI/Styles/Appearance/Display/VisibilityStyleOperation.swift
+++ b/Sources/WebUI/Styles/Layout/Display/VisibilityStyleOperation.swift
@@ -9,14 +9,14 @@ public struct VisibilityStyleOperation: StyleOperation, @unchecked Sendable {
public struct Parameters {
/// Whether the element should be hidden
public let isHidden: Bool
-
+
/// Creates parameters for visibility styling
///
/// - Parameter isHidden: Whether the element should be hidden
public init(isHidden: Bool = true) {
self.isHidden = isHidden
}
-
+
/// Creates parameters from a StyleParameters container
///
/// - Parameter params: The style parameters container
@@ -27,7 +27,7 @@ public struct VisibilityStyleOperation: StyleOperation, @unchecked Sendable {
)
}
}
-
+
/// Applies the visibility style and returns the appropriate CSS classes
///
/// - Parameter params: The parameters for visibility styling
@@ -39,10 +39,10 @@ public struct VisibilityStyleOperation: StyleOperation, @unchecked Sendable {
return []
}
}
-
+
/// Shared instance for use across the framework
public static let shared = VisibilityStyleOperation()
-
+
/// Private initializer to enforce singleton usage
private init() {}
}
@@ -75,7 +75,7 @@ extension Element {
on modifiers: Modifier...
) -> Element {
let params = VisibilityStyleOperation.Parameters(isHidden: isHidden)
-
+
return VisibilityStyleOperation.shared.applyToElement(
self,
params: params,
@@ -93,7 +93,7 @@ extension ResponsiveBuilder {
@discardableResult
public func hidden(_ isHidden: Bool = true) -> ResponsiveBuilder {
let params = VisibilityStyleOperation.Parameters(isHidden: isHidden)
-
+
return VisibilityStyleOperation.shared.applyToBuilder(self, params: params)
}
}
@@ -105,6 +105,6 @@ extension ResponsiveBuilder {
/// - Returns: A responsive modification for visibility.
public func hidden(_ isHidden: Bool = true) -> ResponsiveModification {
let params = VisibilityStyleOperation.Parameters(isHidden: isHidden)
-
+
return VisibilityStyleOperation.shared.asModification(params: params)
-}
\ No newline at end of file
+}
diff --git a/Sources/WebUI/Styles/Base/MarginsStyleOperation.swift b/Sources/WebUI/Styles/Layout/MarginsStyleOperation.swift
similarity index 100%
rename from Sources/WebUI/Styles/Base/MarginsStyleOperation.swift
rename to Sources/WebUI/Styles/Layout/MarginsStyleOperation.swift
diff --git a/Sources/WebUI/Styles/Positioning/Overflow/OverflowStyleOperation.swift b/Sources/WebUI/Styles/Layout/Overflow/OverflowStyleOperation.swift
similarity index 100%
rename from Sources/WebUI/Styles/Positioning/Overflow/OverflowStyleOperation.swift
rename to Sources/WebUI/Styles/Layout/Overflow/OverflowStyleOperation.swift
diff --git a/Sources/WebUI/Styles/Positioning/Overflow/OverflowTypes.swift b/Sources/WebUI/Styles/Layout/Overflow/OverflowTypes.swift
similarity index 100%
rename from Sources/WebUI/Styles/Positioning/Overflow/OverflowTypes.swift
rename to Sources/WebUI/Styles/Layout/Overflow/OverflowTypes.swift
diff --git a/Sources/WebUI/Styles/Base/PaddingStyleOperation.swift b/Sources/WebUI/Styles/Layout/PaddingStyleOperation.swift
similarity index 100%
rename from Sources/WebUI/Styles/Base/PaddingStyleOperation.swift
rename to Sources/WebUI/Styles/Layout/PaddingStyleOperation.swift
diff --git a/Sources/WebUI/Styles/Positioning/Position/PositionStyleOperation.swift b/Sources/WebUI/Styles/Layout/Position/PositionStyleOperation.swift
similarity index 100%
rename from Sources/WebUI/Styles/Positioning/Position/PositionStyleOperation.swift
rename to Sources/WebUI/Styles/Layout/Position/PositionStyleOperation.swift
diff --git a/Sources/WebUI/Styles/Positioning/Position/PositionTypes.swift b/Sources/WebUI/Styles/Layout/Position/PositionTypes.swift
similarity index 100%
rename from Sources/WebUI/Styles/Positioning/Position/PositionTypes.swift
rename to Sources/WebUI/Styles/Layout/Position/PositionTypes.swift
diff --git a/Sources/WebUI/Styles/Positioning/Scroll/ScrollStyleOperation.swift b/Sources/WebUI/Styles/Layout/Scroll/ScrollStyleOperation.swift
similarity index 100%
rename from Sources/WebUI/Styles/Positioning/Scroll/ScrollStyleOperation.swift
rename to Sources/WebUI/Styles/Layout/Scroll/ScrollStyleOperation.swift
diff --git a/Sources/WebUI/Styles/Positioning/Scroll/ScrollTypes.swift b/Sources/WebUI/Styles/Layout/Scroll/ScrollTypes.swift
similarity index 100%
rename from Sources/WebUI/Styles/Positioning/Scroll/ScrollTypes.swift
rename to Sources/WebUI/Styles/Layout/Scroll/ScrollTypes.swift
diff --git a/Sources/WebUI/Styles/Base/Sizing/SizingStyleOperation.swift b/Sources/WebUI/Styles/Layout/Sizing/SizingStyleOperation.swift
similarity index 99%
rename from Sources/WebUI/Styles/Base/Sizing/SizingStyleOperation.swift
rename to Sources/WebUI/Styles/Layout/Sizing/SizingStyleOperation.swift
index 54b9c2f8..2d3fa101 100644
--- a/Sources/WebUI/Styles/Base/Sizing/SizingStyleOperation.swift
+++ b/Sources/WebUI/Styles/Layout/Sizing/SizingStyleOperation.swift
@@ -155,7 +155,7 @@ public struct SizingStyleOperation: StyleOperation, @unchecked Sendable {
return classes
}
-
+
public func applyFrameClasses(params: FrameParameters) -> [String] {
applyClasses(params: params)
}
diff --git a/Sources/WebUI/Styles/Base/Sizing/SizingTypes.swift b/Sources/WebUI/Styles/Layout/Sizing/SizingTypes.swift
similarity index 100%
rename from Sources/WebUI/Styles/Base/Sizing/SizingTypes.swift
rename to Sources/WebUI/Styles/Layout/Sizing/SizingTypes.swift
diff --git a/Sources/WebUI/Styles/Base/SpacingStyleOperation.swift b/Sources/WebUI/Styles/Layout/SpacingStyleOperation.swift
similarity index 100%
rename from Sources/WebUI/Styles/Base/SpacingStyleOperation.swift
rename to Sources/WebUI/Styles/Layout/SpacingStyleOperation.swift
diff --git a/Sources/WebUI/Styles/Positioning/ZIndexStyleOperation.swift b/Sources/WebUI/Styles/Layout/ZIndexStyleOperation.swift
similarity index 100%
rename from Sources/WebUI/Styles/Positioning/ZIndexStyleOperation.swift
rename to Sources/WebUI/Styles/Layout/ZIndexStyleOperation.swift
diff --git a/Sources/WebUI/Styles/Core/ResponsiveAlias.swift b/Sources/WebUI/Styles/Responsive/ResponsiveAlias.swift
similarity index 95%
rename from Sources/WebUI/Styles/Core/ResponsiveAlias.swift
rename to Sources/WebUI/Styles/Responsive/ResponsiveAlias.swift
index 2c0972b1..0ea440fe 100644
--- a/Sources/WebUI/Styles/Core/ResponsiveAlias.swift
+++ b/Sources/WebUI/Styles/Responsive/ResponsiveAlias.swift
@@ -10,6 +10,6 @@ extension Element {
/// - Parameter content: A closure defining responsive style configurations using the result builder.
/// - Returns: An element with responsive styles applied.
public func responsive(@ResponsiveStyleBuilder _ content: () -> ResponsiveModification) -> Element {
- return on(content)
+ on(content)
}
-}
\ No newline at end of file
+}
diff --git a/Sources/WebUI/Styles/Core/ResponsiveModifier.swift b/Sources/WebUI/Styles/Responsive/ResponsiveModifier.swift
similarity index 100%
rename from Sources/WebUI/Styles/Core/ResponsiveModifier.swift
rename to Sources/WebUI/Styles/Responsive/ResponsiveModifier.swift
diff --git a/Sources/WebUI/Styles/Core/ResponsiveStyleBuilder.swift b/Sources/WebUI/Styles/Responsive/ResponsiveStyleBuilder.swift
similarity index 100%
rename from Sources/WebUI/Styles/Core/ResponsiveStyleBuilder.swift
rename to Sources/WebUI/Styles/Responsive/ResponsiveStyleBuilder.swift
diff --git a/Sources/WebUI/Styles/Core/ResponsiveStyleModifiers.swift b/Sources/WebUI/Styles/Responsive/ResponsiveStyleModifiers.swift
similarity index 100%
rename from Sources/WebUI/Styles/Core/ResponsiveStyleModifiers.swift
rename to Sources/WebUI/Styles/Responsive/ResponsiveStyleModifiers.swift
diff --git a/Sources/WebUI/Styles/Base/Font/FontStyleOperation.swift b/Sources/WebUI/Styles/Typography/Font/FontStyleOperation.swift
similarity index 100%
rename from Sources/WebUI/Styles/Base/Font/FontStyleOperation.swift
rename to Sources/WebUI/Styles/Typography/Font/FontStyleOperation.swift
diff --git a/Sources/WebUI/Styles/Base/Font/FontTypes.swift b/Sources/WebUI/Styles/Typography/Font/FontTypes.swift
similarity index 100%
rename from Sources/WebUI/Styles/Base/Font/FontTypes.swift
rename to Sources/WebUI/Styles/Typography/Font/FontTypes.swift
diff --git a/Sources/WebUIMarkdown/Documentation.docc/MarkdownBasics.md b/Sources/WebUIMarkdown/Documentation.docc/MarkdownBasics.md
new file mode 100644
index 00000000..5da2b889
--- /dev/null
+++ b/Sources/WebUIMarkdown/Documentation.docc/MarkdownBasics.md
@@ -0,0 +1,74 @@
+# Markdown Basics
+
+Learn how to use WebUIMarkdown to render Markdown content in your web applications.
+
+## Overview
+
+WebUIMarkdown provides seamless integration of Markdown content within your WebUI applications.
+
+## Basic Usage
+
+```swift
+import WebUI
+import WebUIMarkdown
+
+struct BlogPost: Website {
+ var body: some HTML {
+ Document {
+ Markdown {
+ """
+ # Welcome to my blog
+
+ This is a paragraph with **bold** and *italic* text.
+
+ - List item 1
+ - List item 2
+ """
+ }
+ }
+ }
+}
+```
+
+## Features
+
+### Supported Markdown Elements
+
+- Headers (H1-H6)
+- Emphasis (bold, italic)
+- Lists (ordered and unordered)
+- Links and images
+- Code blocks with syntax highlighting
+- Tables
+- Blockquotes
+- Task lists
+
+### Syntax Highlighting
+
+WebUIMarkdown includes built-in syntax highlighting for code blocks:
+
+```swift
+Markdown {
+ """
+ ```swift
+ func hello() {
+ print("Hello, World!")
+ }
+ ```
+ """
+}
+.syntaxHighlighting(.github)
+```
+
+## Topics
+
+### Getting Started
+
+-
+-
+
+### Advanced Features
+
+-
+- ``MarkdownParser``
+- ``HighlightingOptions``
diff --git a/StructuredData.swift b/StructuredData.swift
deleted file mode 100644
index a9a54c09..00000000
--- a/StructuredData.swift
+++ /dev/null
@@ -1,506 +0,0 @@
-import Foundation
-
-/// Represents structured data in JSON-LD format for rich snippets in search results.
-///
-/// The `StructuredData` struct provides a type-safe way to define structured data
-/// following various schema.org schemas like Article, Product, Organization, etc.
-public struct StructuredData {
-
- /// The schema type for the structured data.
- public enum SchemaType: String {
- case article = "Article"
- case blogPosting = "BlogPosting"
- case breadcrumbList = "BreadcrumbList"
- case course = "Course"
- case event = "Event"
- case faqPage = "FAQPage"
- case howTo = "HowTo"
- case localBusiness = "LocalBusiness"
- case organization = "Organization"
- case person = "Person"
- case product = "Product"
- case recipe = "Recipe"
- case review = "Review"
- case website = "WebSite"
- }
-
- /// The type of schema used for this structured data.
- public let type: SchemaType
-
- /// The raw data to be included in the structured data.
- private let data: [String: Any]
-
- /// Returns a copy of the raw data dictionary.
- ///
- /// - Returns: A dictionary containing the structured data properties.
- public func getData() -> [String: Any] {
- data
- }
-
- /// Creates structured data for an Article.
- ///
- /// - Parameters:
- /// - headline: The title of the article.
- /// - image: The URL to the featured image of the article.
- /// - author: The name or URL of the author.
- /// - publisher: Optional publisher, either as a StructuredData person or organization, or as a String name.
- /// - datePublished: The date the article was published.
- /// - dateModified: The date the article was last modified.
- /// - description: A short description of the article content.
- /// - url: The URL of the article.
- /// - Returns: A structured data object for an article.
- ///
- /// - Example:
- /// ```swift
- /// // Using a String for publisher
- /// let articleData = StructuredData.article(
- /// headline: "How to Use WebUI",
- /// image: "https://example.com/images/article.jpg",
- /// author: "John Doe",
- /// publisher: "WebUI Blog",
- /// datePublished: Date(),
- /// description: "A guide to using WebUI for Swift developers"
- /// )
- ///
- /// // Using a StructuredData organization as publisher
- /// let orgPublisher = StructuredData.organization(
- /// name: "WebUI Technologies",
- /// logo: "https://example.com/logo.png",
- /// url: "https://example.com"
- /// )
- ///
- /// let articleWithOrg = StructuredData.article(
- /// headline: "How to Use WebUI",
- /// image: "https://example.com/images/article.jpg",
- /// author: "John Doe",
- /// publisher: orgPublisher,
- /// datePublished: Date(),
- /// description: "A guide to using WebUI for Swift developers"
- /// )
- ///
- /// // Without a publisher
- /// let minimalArticle = StructuredData.article(
- /// headline: "Quick Tips",
- /// image: "https://example.com/images/tips.jpg",
- /// author: "Alex Developer",
- /// datePublished: Date()
- /// )
- /// ```
- ///
- /// - Note: For more control over the publisher entity, use the overloaded version
- /// of this method that accepts a StructuredData object as the publisher parameter.
- public static func article(
- headline: String,
- image: String,
- author: String,
- publisher: Any? = nil,
- datePublished: Date,
- dateModified: Date? = nil,
- description: String? = nil,
- url: String? = nil
- ) -> StructuredData {
- var data: [String: Any] = [
- "headline": headline,
- "image": image,
- "author": ["@type": "Person", "name": author],
- "publisher": ["@type": "Organization", "name": publisher],
- "datePublished": ISO8601DateFormatter().string(from: datePublished),
- ]
-
- // Handle different publisher types
- if let publisher = publisher {
- if let publisherName = publisher as? String {
- data["publisher"] = ["@type": "Organization", "name": publisherName]
- } else if let publisherData = publisher as? StructuredData {
- if publisherData.type == .organization || publisherData.type == .person {
- // Extract the raw data from the structured data object
- let publisherDict = publisherData.getData()
- var typeDict = publisherDict
- typeDict["@type"] = publisherData.type.rawValue
- data["publisher"] = typeDict
- }
- }
- }
-
- if let dateModified = dateModified {
- data["dateModified"] = ISO8601DateFormatter().string(from: dateModified)
- }
-
- if let description = description {
- data["description"] = description
- }
-
- if let url = url {
- data["url"] = url
- }
-
- return StructuredData(type: .article, data: data)
- }
-
- /// Creates structured data for a product.
- ///
- /// - Parameters:
- /// - name: The name of the product.
- /// - image: The URL to the product image.
- /// - description: A description of the product.
- /// - sku: The Stock Keeping Unit identifier.
- /// - brand: The brand name of the product.
- /// - offers: The offer details (price, availability, etc.).
- /// - review: Optional review information.
- /// - Returns: A structured data object for a product.
- ///
- /// - Example:
- /// ```swift
- /// let productData = StructuredData.product(
- /// name: "Swift WebUI Course",
- /// image: "https://example.com/images/course.jpg",
- /// description: "Master WebUI development with Swift",
- /// sku: "WEBUI-101",
- /// brand: "Swift Academy",
- /// offers: ["price": "99.99", "priceCurrency": "USD", "availability": "InStock"]
- /// )
- /// ```
- public static func product(
- name: String,
- image: String,
- description: String,
- sku: String,
- brand: String,
- offers: [String: Any],
- review: [String: Any]? = nil
- ) -> StructuredData {
- var data: [String: Any] = [
- "name": name,
- "image": image,
- "description": description,
- "sku": sku,
- "brand": ["@type": "Brand", "name": brand],
- "offers": offers.merging(["@type": "Offer"]) { _, new in new },
- ]
-
- if let review = review {
- data["review"] = review.merging(["@type": "Review"]) { _, new in new }
- }
-
- return StructuredData(type: .product, data: data)
- }
-
- /// Creates structured data for an organization.
- ///
- /// - Parameters:
- /// - name: The name of the organization.
- /// - logo: The URL to the organization's logo.
- /// - url: The URL of the organization's website.
- /// - contactPoint: Optional contact information.
- /// - sameAs: Optional array of URLs that also represent the entity.
- /// - Returns: A structured data object for an organization.
- ///
- /// - Example:
- /// ```swift
- /// let orgData = StructuredData.organization(
- /// name: "WebUI Technologies",
- /// logo: "https://example.com/logo.png",
- /// url: "https://example.com",
- /// sameAs: ["https://twitter.com/webui", "https://github.com/webui"]
- /// )
- /// ```
- public static func organization(
- name: String,
- logo: String,
- url: String,
- contactPoint: [String: Any]? = nil,
- sameAs: [String]? = nil
- ) -> StructuredData {
- var data: [String: Any] = [
- "name": name,
- "logo": logo,
- "url": url,
- ]
-
- if let contactPoint = contactPoint {
- data["contactPoint"] = contactPoint.merging(["@type": "ContactPoint"]) { _, new in new }
- }
-
- if let sameAs = sameAs {
- data["sameAs"] = sameAs
- }
-
- return StructuredData(type: .organization, data: data)
- }
-
- /// Creates structured data for a person.
- ///
- /// - Parameters:
- /// - name: The name of the person.
- /// - givenName: The given (first) name of the person.
- /// - familyName: The family (last) name of the person.
- /// - image: The URL to an image of the person.
- /// - jobTitle: The person's job title.
- /// - email: The person's email address.
- /// - telephone: The person's telephone number.
- /// - url: The URL of the person's website or profile.
- /// - address: Optional address information.
- /// - birthDate: The person's date of birth.
- /// - sameAs: Optional array of URLs that also represent the person (social profiles).
- /// - Returns: A structured data object for a person.
- ///
- /// - Example:
- /// ```swift
- /// let personData = StructuredData.person(
- /// name: "Jane Doe",
- /// givenName: "Jane",
- /// familyName: "Doe",
- /// jobTitle: "Software Engineer",
- /// url: "https://janedoe.com",
- /// sameAs: ["https://twitter.com/janedoe", "https://github.com/janedoe"]
- /// )
- /// ```
- public static func person(
- name: String,
- givenName: String? = nil,
- familyName: String? = nil,
- image: String? = nil,
- jobTitle: String? = nil,
- email: String? = nil,
- telephone: String? = nil,
- url: String? = nil,
- address: [String: Any]? = nil,
- birthDate: Date? = nil,
- sameAs: [String]? = nil
- ) -> StructuredData {
- var data: [String: Any] = [
- "name": name
- ]
-
- if let givenName = givenName {
- data["givenName"] = givenName
- }
-
- if let familyName = familyName {
- data["familyName"] = familyName
- }
-
- if let image = image {
- data["image"] = image
- }
-
- if let jobTitle = jobTitle {
- data["jobTitle"] = jobTitle
- }
-
- if let email = email {
- data["email"] = email
- }
-
- if let telephone = telephone {
- data["telephone"] = telephone
- }
-
- if let url = url {
- data["url"] = url
- }
-
- if let address = address {
- data["address"] = address.merging(["@type": "PostalAddress"]) { _, new in new }
- }
-
- if let birthDate = birthDate {
- data["birthDate"] = ISO8601DateFormatter().string(from: birthDate)
- }
-
- if let sameAs = sameAs {
- data["sameAs"] = sameAs
- }
-
- return StructuredData(type: .person, data: data)
- }
-
- /// Creates structured data for breadcrumbs navigation.
- ///
- /// - Parameter items: Array of breadcrumb items with name, item (URL), and position.
- /// - Returns: A structured data object for breadcrumbs navigation.
- ///
- /// - Example:
- /// ```swift
- /// let breadcrumbsData = StructuredData.breadcrumbs([
- /// ["name": "Home", "item": "https://example.com", "position": 1],
- /// ["name": "Blog", "item": "https://example.com/blog", "position": 2],
- /// ["name": "Article Title", "item": "https://example.com/blog/article", "position": 3]
- /// ])
- /// ```
- public static func breadcrumbs(_ items: [[String: Any]]) -> StructuredData {
- let itemListElements = items.map { item in
- var element: [String: Any] = ["@type": "ListItem"]
-
- if let name = item["name"] as? String {
- element["name"] = name
- }
-
- if let itemUrl = item["item"] as? String {
- element["item"] = itemUrl
- }
-
- if let position = item["position"] as? Int {
- element["position"] = position
- }
-
- return element
- }
-
- return StructuredData(type: .breadcrumbList, data: ["itemListElement": itemListElements])
- }
-
- /// Creates structured data for an Article with a StructuredData publisher.
- ///
- /// - Parameters:
- /// - headline: The title of the article.
- /// - image: The URL to the featured image of the article.
- /// - author: The name or URL of the author.
- /// - publisher: Optional StructuredData object representing the publisher as a person or organization.
- /// - datePublished: The date the article was published.
- /// - dateModified: The date the article was last modified.
- /// - description: A short description of the article content.
- /// - url: The URL of the article.
- /// - Returns: A structured data object for an article.
- ///
- /// - Example:
- /// ```swift
- /// // Using an organization as publisher
- /// let organization = StructuredData.organization(
- /// name: "WebUI Technologies",
- /// logo: "https://example.com/logo.png",
- /// url: "https://example.com"
- /// )
- ///
- /// let articleWithOrg = StructuredData.article(
- /// headline: "How to Use WebUI",
- /// image: "https://example.com/images/article.jpg",
- /// author: "John Doe",
- /// publisher: organization,
- /// datePublished: Date(),
- /// description: "A guide to using WebUI for Swift developers"
- /// )
- ///
- /// // Using a person as publisher
- /// let personPublisher = StructuredData.person(
- /// name: "Jane Doe",
- /// url: "https://janedoe.com"
- /// )
- ///
- /// let articleWithPerson = StructuredData.article(
- /// headline: "My WebUI Journey",
- /// image: "https://example.com/images/journey.jpg",
- /// author: "John Smith",
- /// publisher: personPublisher,
- /// datePublished: Date(),
- /// description: "Personal experiences with WebUI framework"
- /// )
- ///
- /// // Without a publisher
- /// let minimalArticle = StructuredData.article(
- /// headline: "Quick Tips",
- /// image: "https://example.com/images/tips.jpg",
- /// author: "Alex Developer",
- /// datePublished: Date()
- /// )
- /// ```
- public static func article(
- headline: String,
- image: String,
- author: String,
- publisher: StructuredData?,
- datePublished: Date,
- dateModified: Date? = nil,
- description: String? = nil,
- url: String? = nil
- ) -> StructuredData {
- var data: [String: Any] = [
- "headline": headline,
- "image": image,
- "author": ["@type": "Person", "name": author],
- "datePublished": ISO8601DateFormatter().string(from: datePublished),
- ]
-
- if let publisher = publisher {
- if publisher.type == .organization || publisher.type == .person {
- // Extract the raw data from the structured data object
- let publisherDict = publisher.getData()
- var typeDict = publisherDict
- typeDict["@type"] = publisher.type.rawValue
- data["publisher"] = typeDict
- }
- }
-
- if let dateModified = dateModified {
- data["dateModified"] = ISO8601DateFormatter().string(from: dateModified)
- }
-
- if let description = description {
- data["description"] = description
- }
-
- if let url = url {
- data["url"] = url
- }
-
- return StructuredData(type: .article, data: data)
- }
-
- /// Creates a custom structured data object with the specified schema type and data.
- ///
- /// - Parameters:
- /// - type: The schema type for the structured data.
- /// - data: The data to include in the structured data.
- /// - Returns: A structured data object with the specified type and data.
- ///
- /// - Example:
- /// ```swift
- /// let customData = StructuredData.custom(
- /// type: .review,
- /// data: [
- /// "itemReviewed": ["@type": "Product", "name": "WebUI Framework"],
- /// "reviewRating": ["@type": "Rating", "ratingValue": "5"],
- /// "author": ["@type": "Person", "name": "Jane Developer"]
- /// ]
- /// )
- /// ```
- public static func custom(type: SchemaType, data: [String: Any]) -> StructuredData {
- StructuredData(type: type, data: data)
- }
-
- /// Initializes a new structured data object with the specified schema type and data.
- ///
- /// - Parameters:
- /// - type: The schema type for the structured data.
- /// - data: The data to include in the structured data.
- public init(type: SchemaType, data: [String: Any]) {
- self.type = type
- self.data = data
- }
-
- /// Converts the structured data to a JSON string.
- ///
- /// - Returns: A JSON string representation of the structured data, or an empty string if serialization fails.
- public func toJSON() -> String {
- var jsonObject: [String: Any] = [
- "@context": "https://schema.org",
- "@type": type.rawValue,
- ]
-
- // Merge the data dictionary with the base JSON object
- for (key, value) in data {
- jsonObject[key] = value
- }
-
- // Try to serialize the JSON object to data
- if let jsonData = try? JSONSerialization.data(
- withJSONObject: jsonObject,
- options: [.prettyPrinted, .withoutEscapingSlashes]
- ) {
- // Convert the data to a string
- return String(data: jsonData, encoding: .utf8) ?? ""
- }
-
- return ""
- }
-}
diff --git a/Tests/WebUITests/ElementTests.swift b/Tests/WebUITests/ElementTests.swift
index 6b359054..b25c1793 100644
--- a/Tests/WebUITests/ElementTests.swift
+++ b/Tests/WebUITests/ElementTests.swift
@@ -544,11 +544,11 @@ import Testing
@Test("Main element")
func testMainElement() async throws {
- let main = Main(id: "content") {
+ let mainElement = Main(id: "content") {
Text { "Main content" }
}
- let rendered = main.render()
+ let rendered = mainElement.render()
#expect(rendered.contains(" String {
- Button(disabled: isDisabled, onClick: onClick) { label }
- // Base styles
- .padding(of: 4)
- .rounded(.md)
- .transition(of: .all, for: 150)
- .font(weight: .medium)
-
- // Conditional primary/secondary styles
- .on {
- if isPrimary {
- background(color: .blue(._600))
- font(color: .gray(._50))
- } else {
- background(color: .gray(._200))
- font(color: .gray(._800))
- border(of: 1, color: .gray(._300))
- }
- }
-
- // Interactive state modifiers
- .on {
- // Hover state
- hover {
- if isPrimary {
- background(color: .blue(._700))
- } else {
- background(color: .gray(._300))
- }
- transform(scale: (x: 102, y: 102))
- }
-
- // Focus state (accessibility)
- focus {
- if isPrimary {
- outline(of: 2, color: .blue(._300))
- } else {
- outline(of: 2, color: .gray(._400))
- }
- outline(style: .solid)
- transform(translateY: -1)
- }
-
- // Active state (when pressing)
- active {
- if isPrimary {
- background(color: .blue(._800))
- } else {
- background(color: .gray(._400))
- }
- transform(scale: (x: 98, y: 98))
- }
-
- // Disabled state
- disabled {
- if isPrimary {
- background(color: .blue(._300))
- } else {
- background(color: .gray(._100))
- font(color: .gray(._400))
- }
- opacity(70)
- cursor(.notAllowed)
- }
- }
- .render()
- }
-}
-
-/// Form Input Component Example
-///
-/// This example demonstrates how to create a form input with various
-/// interactive states including placeholder styling and ARIA states.
-struct FormInput: HTML {
- var id: String
- var label: String
- var placeholder: String = ""
- var isRequired: Bool = false
- var isInvalid: Bool = false
- var value: String? = nil
-
- func render() -> String {
- Div {
- Label(for: id) { label }
- .font(size: .sm)
- .font(weight: .medium)
- .font(color: .gray(._700))
- .on {
- if isRequired {
- after {
- font(color: .red(._500))
- }
- }
- }
-
- Input(id: id, value: value, placeholder: placeholder, required: isRequired)
- .padding(of: 3)
- .rounded(.md)
- .border(of: 1, color: .gray(._300))
- .width(.full)
- .font(size: .sm)
- .transition(of: .all, for: 150)
-
- // Interactive state styling
- .on {
- // Placeholder styling
- placeholder {
- font(color: .gray(._400))
- font(weight: .light)
- }
-
- // Focus state
- focus {
- border(of: 1, color: .blue(._500))
- shadow(of: .sm, color: .blue(._100))
- }
-
- // When the field is invalid
- if isInvalid {
- border(of: 1, color: .red(._500))
-
- // Invalid + focus state
- focus {
- border(of: 1, color: .red(._500))
- shadow(of: .sm, color: .red(._100))
- }
- }
-
- // ARIA required state
- ariaRequired {
- border(of: 1, style: .solid)
- }
- }
- }
- .render()
- }
-}
-
-/// Navigation Menu Item Example
-///
-/// This example demonstrates how to create an accessible navigation menu item
-/// with different states for hover, focus, active, and selected.
-struct NavMenuItem: HTML {
- var label: String
- var href: String
- var isSelected: Bool = false
-
- func render() -> String {
- Link(to: href) { label }
- .padding(vertical: 2, horizontal: 4)
- .rounded(.md)
- .font(size: .sm)
- .transition(of: .all, for: 150)
-
- // Base state
- .on {
- if isSelected {
- font(weight: .semibold)
- font(color: .blue(._700))
- background(color: .blue(._50))
- } else {
- font(weight: .normal)
- font(color: .gray(._700))
- }
- }
-
- // Interactive states
- .on {
- // Hover state
- hover {
- if !isSelected {
- background(color: .gray(._100))
- } else {
- background(color: .blue(._100))
- }
- }
-
- // Focus state for keyboard navigation
- focus {
- outline(of: 2, color: .blue(._300))
- outline(style: .solid)
- outline(offset: 1)
- }
-
- // Active state (when pressing)
- active {
- if !isSelected {
- background(color: .gray(._200))
- } else {
- background(color: .blue(._200))
- }
- transform(scale: (x: 98, y: 98))
- }
-
- // ARIA selected state for screen readers
- ariaSelected {
- font(weight: .semibold)
- }
- }
- .render()
- }
-}
-
-/// Example usage in a page context
-struct InteractiveComponentsDemo: HTML {
- func render() -> String {
- return Document(title: "Interactive Components Demo") {
- Section {
- Heading(level: 1) { "Interactive Components Demo" }
- .font(size: .xl2)
- .padding(bottom: 6)
-
- // Buttons section
- Div {
- Heading(level: 2) { "Buttons" }
- .font(size: .xl)
- .padding(bottom: 4)
-
- Div {
- InteractiveButton(label: "Primary Button")
- InteractiveButton(label: "Secondary Button", isPrimary: false)
- InteractiveButton(label: "Disabled Button", isDisabled: true)
- }
- .spacing(of: 4)
- .display(.flex)
- }
- .padding(bottom: 8)
-
- // Form section
- Div {
- Heading(level: 2) { "Form Inputs" }
- .font(size: .xl)
- .padding(bottom: 4)
-
- Div {
- FormInput(id: "name", label: "Name", placeholder: "Enter your name")
- FormInput(id: "email", label: "Email", placeholder: "Enter your email", isRequired: true)
- FormInput(id: "password", label: "Password", placeholder: "Enter your password", isInvalid: true)
- }
- .spacing(of: 4, along: .vertical)
- }
- .padding(bottom: 8)
-
- // Navigation section
- Div {
- Heading(level: 2) { "Navigation" }
- .font(size: .xl)
- .padding(bottom: 4)
-
- Nav {
- Div {
- NavMenuItem(label: "Home", href: "/", isSelected: true)
- NavMenuItem(label: "Products", href: "/products")
- NavMenuItem(label: "About", href: "/about")
- NavMenuItem(label: "Contact", href: "/contact")
- }
- .display(.flex)
- .spacing(of: 2)
- }
- }
- }
- .padding(of: 8)
- .maxWidth(.character(80))
- .margins(at: .horizontal, auto: true)
- }
- .render()
- }
-}
\ No newline at end of file