++ed by:
1 non-PAUSE user
Author image sysread


HTML::Untidy - yet another way to write HTML quickly and programmatically


version 0.02


  use HTML::Untidy;

  sub bootstrap4_modal {
    my ($content, $indent) = @_;

    # Set number of spaces to indent
    local $HTML::Untidy::INDENT = $indent || 0;

    div {
      class 'modal';

      div {
        class 'modal-dialog';

        div {
          class 'modal-header';
          h5 { class 'modal-title'; text 'Modal title' };
          button { class 'close'; attr 'type' => 'button', 'data-dismiss' => 'modal'; raw '×'; };

        div { class 'modal-body'; raw $content; };

        div {
          class 'modal-footer';
          button { class 'btn btn-primary'; attr 'type' => 'button'; text 'Save changes'; };
          button { class 'btn btn-secondary'; attr 'type' => 'button', 'data-dismiss' => 'modal'; text 'Close'; };

  my $modal = bootstrap4_modal('Here is my modal!', 2);

  # Resulting string:
  # <div class="modal">
  #   <div class="modal-dialog">
  #     <div class="modal-header">
  #       <h5 class="modal-title">
  #         Modal title
  #       </h5>
  #       <button class="close" type="button" data-dismiss="modal">
  #         &times;
  #       </button>
  #     </div>
  #     <div class="modal-body">
  #       Here is my modal!
  #     </div>
  #     <div class="modal-footer">
  #       <button class="btn btn-primary" type="button">
  #         Save changes
  #       </button>
  #       <button class="btn btn-secondary" type="button" data-dismiss="modal">
  #         Close
  #       </button>
  #     </div>
  #   </div>
  # </div>


This is adapted from a series of short scripts I had written for use in vim. I figured someone else might find them useful so I cleaned them up a bit and spent the rest of the afternoon trying to figure out what the hell to call it.




Sometimes I don't want to have a proper separation of concerns. Sometimes I just want to generate HTML programmatically and easily from perl because I hate writing HTML or I want to build a page from componentized parts.

Isn't this just CGI::HTML::Functions? Yeah, kinda. But so are React and Vue. Complain and maybe I'll write Inline::HTML.

I think I got all of the HTML5 tags added. If there are any I missed, it's simple enough to add your own.

  *blink   = sub(&){ unshift @_, 'blink';   goto \&element; });
  *marquee = sub(&){ unshift @_, 'marquee'; goto \&element; });



Base functions everything else is built from.


Accepts a tag name and subroutine to generate its components. In scalar context, returns a formatted string of HTML. In void context within another call to element, the generated string is appended to the parent element.


Accepts a list of strings to use as the class list for the element. Multiple classes may share the same string, separated by whitespace. No deduplication is done on these.


Takes key/value pairs as a hash and generates attributes. Again, no deduplication, and the attributes are displayed in the order in which they are passed.


Adds a property which has no value (e.g. disabled or hidden).


Adds escaped text to the body of the tag.


Adds unescaped text to the body of the tag.


Adds an HTML comment to the body of the tag.


If you want indentation, set $HTML::Untidy::INDENT to the number of spaces you want per level.

  local $HTML::Untidy::INDENT = 2;
  element 'div', sub{
    element 'button', sub{
      class 'btn btn-primary';
      attr id => 'some-button', 'data-toggle' => 'modal', 'data-target' => 'my-modal-id';

      if ($some_condition) {
        text 'Click me';
      else {
        prop 'disabled' if $some_condition;
        text "Don't click me";


Exports the following curried aliases of "element" for the most commonly used HTML tags.

  html head body title meta link script style h1 h2 h3 h4 h5 h6 div p hr pre nav
  code img a b i u em strong sup sub small table tbody thead tr th td ul dl ol
  li dd dt form input textarea select option button label canvas


Exports every HTML5 tag I could find.

  a abbr address area article aside audio b base bdi bdo blockquote body br
  button canvas caption cite code col colgroup data datalist dd del details dfn
  dialog div dl dt em embed fieldset figcaption figure footer form h1 h2 h3 h4
  h5 h6 head header hgroup hr html i iframe img input ins kbd label legend li
  link main map mark menu menuitem meta meter nav noframes noscript object ol
  optgroup option output p param picture pre progress q rp rt rtc ruby s samp
  script section select slot small source span strong style sub summary sup
  table tbody td template textarea tfoot th thead time title tr track u ul var
  video wbr




Jeff Ober <sysread@fastmail.fm>




Jeff Ober <sysread@fastmail.fm>


This software is copyright (c) 2017 by Jeff Ober.

This is free software; you can redistribute it and/or modify it under the same terms as the Perl 5 programming language system itself.