{"id":20199637,"url":"https://github.com/christopher-r-perkins/htrb","last_synced_at":"2026-05-31T01:31:19.471Z","repository":{"id":198764853,"uuid":"700007886","full_name":"Christopher-R-Perkins/htrb","owner":"Christopher-R-Perkins","description":"A DSL for HTML Templating","archived":false,"fork":false,"pushed_at":"2024-04-27T23:03:01.000Z","size":46,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"main","last_synced_at":"2025-12-04T01:53:00.221Z","etag":null,"topics":["gem","hypertext","library","ruby"],"latest_commit_sha":null,"homepage":"https://rubygems.org/gems/htrb","language":"Ruby","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/Christopher-R-Perkins.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2023-10-03T19:07:51.000Z","updated_at":"2024-04-27T23:03:04.000Z","dependencies_parsed_at":null,"dependency_job_id":"46c62b39-63d6-4cc5-9091-2a7b6ce70c67","html_url":"https://github.com/Christopher-R-Perkins/htrb","commit_stats":null,"previous_names":["quasarinova/htrb","christopher-r-perkins/htrb"],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/Christopher-R-Perkins/htrb","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Christopher-R-Perkins%2Fhtrb","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Christopher-R-Perkins%2Fhtrb/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Christopher-R-Perkins%2Fhtrb/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Christopher-R-Perkins%2Fhtrb/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/Christopher-R-Perkins","download_url":"https://codeload.github.com/Christopher-R-Perkins/htrb/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Christopher-R-Perkins%2Fhtrb/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":33225509,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-19T15:49:41.270Z","status":"ssl_error","status_checked_at":"2026-05-19T15:49:22.917Z","response_time":58,"last_error":"SSL_connect returned=1 errno=0 peeraddr=140.82.121.6:443 state=error: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["gem","hypertext","library","ruby"],"created_at":"2024-11-14T04:38:19.486Z","updated_at":"2026-05-19T17:14:33.667Z","avatar_url":"https://github.com/Christopher-R-Perkins.png","language":"Ruby","funding_links":[],"categories":[],"sub_categories":[],"readme":"# HTRB - HTML DSL Gem\n\n[![Gem Version](https://badge.fury.io/rb/htrb.svg)](https://badge.fury.io/rb/htrb)\n\n**HTRB** is a DSL for creating dynamic HTML components with Ruby.\n\n## Table of Contents\n- [General Info](#general-info)\n- [Use](#use)\n  - [Installation](#installation)\n  - [DSL](#dsl)\n      - [Warning](#warning)\n  - [HTRB#html](#htrbhtml)\n  - [HTRB#fragment](#htrbfragment)\n  - [HTRB#document](#htrbdocument)\n- [Custom Components](#custom-components)\n  - [Container Components](#container-components)\n- [Reference](#reference)\n  - [HTRB::HtmlNode](#htrbhtmlnode)\n      - [HtmlNode#initialize](#htmlnodeinitialize)\n      - [HtmlNode#parent](#htmlnodeparent)\n      - [HtmlNode#append](#htmlnodeappend)\n      - [HtmlNode#insert](#htmlnodeinsert)\n      - [HtmlNode#remove](#htmlnoderemove)\n      - [HtmlNode#to_s](#htmlnodeto_s)\n      - [HtmlNode#to_pretty](#htmlnodeto_pretty)\n  - [HTRB::Document](#htrbdocument)\n      - [Document#initialize](#documentinitialize)\n      - [Document#head](#documenthead)\n      - [Document#body](#documentbody)\n      - [Document#title](#documenttitle)\n      - [Document#to_s](#documentto_s)\n      - [Document#to_pretty](#documentto_pretty)\n\n\n## General Info\n\n**HTRB** allows you to write HTML inside your Ruby code through the use of a DSL. It was inspired by [JSX](https://react.dev/learn/writing-markup-with-jsx) and [Hypertext](https://github.com/soveran/hypertext).\n\n**HTRB** allows you to seamlessly write HTML along with your Ruby code. In addition, it allows you to write your own dynamic custom HTML components which can be inserted in with regular HTML to make truly dynamic content.\n\nIt has been designed with technologies like [htmx](https://htmx.org/), [Alpine.js](https://alpinejs.dev/), and [Tailwind CSS](https://tailwindcss.com/) in mind to allow easy rendering of HTML fragments on the backend.\n\nTo see a sample app using HTRB, [checkout this project](https://github.com/Christopher-R-Perkins/blink-out-htmx)\n\n## Use\n### Installation\n**HTRB** require Ruby 3.0.0 or higher.\n\nTo use **HTRB**, first you need to install the gem:\n\n```bash\ngem install htrb\n```\n\nThen to use it in your own code, you need to `require` it:\n\n```ruby\nrequire 'htrb'\n```\n\n### DSL\n\nIn general, where applicable, **HTRB** provides methods that mimic html tags. Inside blocks related to HTML, you may call these methods to add the particular tag to the HTML as a child. Every non-depricated HTML5 tag is available in syntax like this:\n\n```ruby\ntag_name! **attributes, \u0026contents-block\n```\n\nSo the `\u003ca\u003e` tag would be represented by the method `a!`, while the `main` tag is represented by the method `main!`.\n\nAttributes are comma separated name-value pairs and can be in either `name: value` or `\"name\" =\u003e value` forms. So `a! href='/'` would be the same as `\u003ca href=\"/\"\u003e\u003c/a\u003e` and `img! src: '/a.png', 'alt' =\u003e 'The letter A'` would be the same as `\u003cimg src=\"/a.png' alt=\"The letter A\"\u003e`. One thing to note is that **HTRB** will automatically change `_` characters in keys to `-` characters, that means `span hx_post: '/accounts'` would become `\u003cspan hx-post=\"/accounts\"\u003e\u003c/span\u003e`\n\nFinally, you can optionally pass a block to every tag, though self-closing tags will raise an `HTRB::SelfClosingTagError` if you try to pass a block to them. These blocks allow you to add child elements to the element you are creating. For example the following will create the equivalent to `\u003ca href=\"/join\"\u003eJoin in!\u003c/a\u003e`:\n\n```ruby\na! href: '/join' do\n  t! 'Join in!'\nend\n```\n\nOh, did I just use `t!`, `\u003ct\u003e` isn't a tag. No, but `t!` is a special method created to make a text node. Its use is just `t! string` and it will make the string a child of the parent element. Do note, `t!` automatically escapes HTML, so if you don't want that use `append` instead(see [HTRB::HtmlNode](#htrbhtmlnode) reference)\n\n#### Warning\n\nDue to the nature of Ruby meta-programming in order to make the DSL work, the blocks are ran in the context of the `HtmlNode` it is being passed to. This means that instance variables and instance method calls will use the context of the object and not the scope the block was created in. Local variables are ok.\n\n```ruby\n@global = 'No'\n\nHTRB.html do\n  p @global # =\u003e nil, referencing HtmlNode instance\n\n  @text = 'Some text'\n  text = @text\n\n  a! href: '/join' do\n    t! text if text == @text\n    # text = 'Some text' due to closure\n    # @text = nil, it is referencing the A instance\n  end\nend # \u003ca href=\"/join\"\u003e\u003c/a\u003e\n```\n\nSo, if you plan on using instance variables in your project, it is best to assign what you need to local variables prior to referencing them in a block.\n\n### HTRB#html\n\nOne of the most useful methods provided by **HTRB** is `html`. It allows you to quickly create a string containing the raw HTML you provide via the DSL inside a block.\n\n```ruby\nHTRB.html do\n  p! id: 'some-text' do\n    t! 'This is just some text inside a paragraph tag'\n  end\n  img! src: \"/smiley.jpg\"\nend\n# =\u003e '\u003cp id=\"some-text\"\u003eThis is just some text inside a paragraph tag\u003c/p\u003e\u003cimg src=\"/smiley.jg\"\u003e'\n```\n\n### HTRB#fragment\n\nThe `fragment` method is very similar to `HTRB#html`. In fact, the `html` does the same thing, except it calls `to_s` on the resulting object and returns the string.\n\n`HTRB#fragment` creates an `HtmlNode` and populates its children with the block you pass to it. You are returned the resulting `HtmlNode` object and are free to do with it as you please. See [HTRB::HtmlNode](#htrbhtmlnode) reference.\n\n### HTRB#document\n\nThe `document` method is a shortcut to create an `HTRB::Document` object. As such it takes all the arguments to construct that a `HTRB::Document` takes and returns the document object. See [HTRB::Document](#htrbdocument) reference.\n\n---\n\n## Custom Components\n\nOne of the most powerful things that **HTRB** can do is allow you to define your own custom components. In general, you do so by creating a class that inherits from `HTRB::Component` and overriding the `render` method:\n\n```ruby\nclass CustomButton \u003c HTRB::Component\n  def render\n    button_text = props.text\n\n    a href: props.href, class: 'button' do\n      t! button_text\n    end\n  end\nend\n```\n\nIn the above example, we create a `CustomButton` component, that when used will create an anchor element with the class `'button'` and a specified `href` and `text`. When we define `CustomButton`, **HTRB** will automatically create a `_custombutton!` method on `HtmlNode` that will allow you to insert this custom button anywhere you could HTML:\n\n```ruby\nHTRB.html do\n  _custombutton! href: '/join', text: 'Join in!'\nend # \u003ca href=\"/join\" class=\"button\"\u003eJoin in!\u003c/a\u003e\n```\n\nTo explain how passing data works, the `props` method will return the attributes passed to your custom tag as a hash, so you are able to access custom data anytime you use the tag. As `props` is an instance method, it is only go to reference your custom tag outside of other HTML blocks. It is best practice to extract the data you need into a local variable if you are going to use it inside another HTML element, like we did with `button_text = props.text`.\n\n### Container Components\n\nBy default, custom components are considered self-closing tags. This means, if you try to pass a block to a custom component, it will raise a `HTRB::SelfClosingTagError`, not allowing you to define the inner contents of your custom tag. We can get around this by overiding the `self_closing?` method and using the `remit` method:\n\n```ruby\nclass CustomContainer \u003c HTRB::Component\n  def render(\u0026contents)\n    div class: 'modal' do\n      remit \u0026contents if block_given?\n    end\n  end\n\n  def self_closing?\n    false\n  end\nend\n```\n\nIn the above, we create a custom container component by overriding the `self_closing?` method and returning false. This allows you to pass a block when using the component. We go one further by using the `remit` method to run the passed block inside the context of our div tag.\n\n`remit` works a lot like the keywork `yield`, but it changes the context of the block to the instance it was called in. You could call it inside a child of your tag or in the tag itself, it doesn't matter. Whatever tags are called inside the block will be added as children to the context it was called in.\n\nTo call this custom container, is just like calling any other custom component. In this case it will be calling the `_customcontainer!` method and passing that a block:\n\n```ruby\nHTRB.html do\n  _customcontainer! do\n    strong! do\n      t! 'In a container'\n    end\n  end\nend # \u003cdiv class=\"modal\"\u003e\u003cstrong\u003eIn a container\u003c/strong\u003e\u003c/div\u003e\n```\n\n---\n\n## Reference\n### HTRB::HtmlNode\n\nEverything in **HTRB** is built around the `HtmlNode` object. Both `HTRB::Component` and `HTRB::Element` are subclasses of it and both automatically make relevant methods inside it for each tag generated.\n\nWhile most probably will not manipulate the `HtmlNode` object, it is good to understand the public interface of it. It provides a simple dom like structure to forming pages, thus may have some use in generating full pages.\n\nThe `HtmlNode` class is private, thus you won't be constructing it in general by itself, but again its good to know.\n\n#### HtmlNode#initialize\n- `initialize(**attributes, \u0026contents)`\n  - Is used to construct an `HtmlNode` object\n  - Will store the `attributes` hash in an instance variable accessible by the private `props` method and then immediately invoke the `render` method passing the `contents` block along\n  - Generally will be called by generated methods for tags and `HTRB.fragment`/`HTRB.html`\n\n#### HtmlNode#parent\n  - Will return the parent node\n- If it has no parent, it will be `nil`\n\n#### HtmlNode#inner_html\n- `inner_html()`\n  - Returns a duplicate of the child array containing all direct child nodes\n- `inner_html(\u0026contents)`\n  - Directly replaces the children with whatever the passed block evaluates to\n  - All children will have their parent property changed to `nil`\n\n#### HtmlNode#append\n- `append(child)`\n  - Appends a `child` to the `HtmlNode` instance\n  - `child` must be a string or `HtmlNode`\n      - If `child` is a string, it will not be HTML escaped unlike `t!`\n      - If `child` is an `HtmlNode`, it will have its parent set to this object and removed from previous parent\n      - if `child` is `self` or an ancestor of `self`, will raise a `HTRB::TagParadoxError`\n  - Returns `child`\n\n#### HtmlNode#insert\n- `insert(child, where, at)`\n  - Inserts a `child` in relation to the `at` child, removing child from previous parent\n  - `child` must be a string or `HtmlNode`\n  - `where` must be either `:before` or `:after`\n  - `at` must be a child of the object\n  - Returns `child`\n\n#### HtmlNode#remove\n- `remove(child)`\n  - Removes the `child` from the object\n  - Returns `child` if it was removed, `nil` otherwise\n\n#### HtmlNode#to_s\n- `to_s()`\n  - converts the object and its children recursively into an html string with no formatting\n\n#### HtmlNode#to_pretty\n- `to_pretty()`\n  - converts the object and its children recursively into an html string with tabs and newlines\n\n### HTRB::Document\n\nThe document object is used to represent an entire HTML document instead of just fragments of one.\n\n#### Document#initialize\n- `initialize(**options, \u0026body_content)`\n  - The constructor will construct the core of the html document\n  - There are only two available `options`\n      - `title:` The title of the page\n      - `head:` A proc to be executed to add to the `\u003chead\u003e\u003c/head\u003e` tag\n          - By default the `\u003ctitle\u003e` and `\u003cmeta charset=\"UTF-8\"\u003e` tags are already defined\n  - `body_content` is the block passed to fill the `\u003cbody\u003e\u003c/body\u003e` tag\n\n#### Document#head\n- `head(\u0026new_contents)`\n  - `new_contents` is the block passed to replace the contents of the `\u003chead\u003e\u003c/head\u003e` tag\n  - The `\u003ctitle\u003e` and `\u003cmeta\u003e` tag are still inserted\n\n#### Document#body\n- `body(\u0026new_contents)`\n  - `new_contents` is the block passed to replace the contents of the `\u003cbody\u003e\u003c/body\u003e` tag\n\n#### Document#title\n- `title()`\n  - Returns the title of the page\n- `title(new_title)`\n  - `new_title` is the new title for the page that replaces the old\n\n#### Document#to_s\n- `to_s()`\n  - converts the document to an html string with no formatting\n\n#### Document#to_pretty\n- `to_pretty()`\n  - converts the document to an html string with tabs and newlines\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fchristopher-r-perkins%2Fhtrb","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fchristopher-r-perkins%2Fhtrb","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fchristopher-r-perkins%2Fhtrb/lists"}