Generated UI Library & Website

LLM-enabled workflows are here and they will only become more relevant in the years to come. To explore the power behind GPT-powered software development, I gave myself a challenge. How quickly can I build a website with content entirely generated by GPT?

Meet getbrutalui.com

A screenshot of the landing page of getbrutalui.com

HTML+Tailwind component libraries are all the rage these days. Tailwind offers their own first-party component library for the low low price of $300. DevDojo recently announced PinesUI as their HTML+Tailwind+AlpineJS component library. And there are plenty more sites commoditizing components for rapid site generation. What harm can one more cause?

Hour 1 - Setup

I got started by cloning my SaaS template GitHub repo, that has a tidy pre-configured tech stack of

  • Backend: FastAPI
  • Templating: Jinja
  • Styles: TailwindCSS
  • Database: SQLite
  • Database Migrations: Alembic

along with a few utilities I like to drag from project to project.

I quickly followed up by asking GPT to implement an index page for me with a navbar and sidebar provided by PinesUI. It got me something that looked like this shell:

A partially blanked-out screenshot to display what BrutalUI looked like at its inception.

Hours 2-3 - Building

Now I need to start creating content for this website. I was inspired by Simon Willison's baked data architectural pattern, as well as the fact that PinesUI also does some data baking. I asked GPT to generate a list of titles and url slugs for a set of commonly provided UI components.

I then asked it to generate some JSON for me containing the component titles and descriptions:

{
    "accordion": {
        "title": "Accordion",
        "description": "A collapsible content component that allows users to toggle between showing and hiding sections of content.",
    },
    "alert": {
        "title": "Alert",
        "description": "A component used to display important messages or notifications to users, typically with a dismissible option.",
    },
    "badge": {
        "title": "Badge",
        "description": "A small visual indicator used to display additional information or status for an element, such as counts or labels.",
    },
    "banner": {
        "title": "Banner",
        "description": "A large horizontal area used to highlight important announcements, promotions, or featured content.",
    },
    "breadcrumbs": {
        "title": "Breadcrumbs",
        "description": "A navigational aid that displays the hierarchical path leading to the current page, helping users understand their location within a website.",
    },
    ...
}

Then, to actually generate the component HTML I used this FastAPI endpoint (with a helper function):

def generate_component_html(component_name, description=None):
    # return "<div>Example</div>"
    component = llm.llm_response(
        f"Write the HTML for a {component_name} component using TailwindCSS and AlpineJS (if applicable). Only return the HTML for the component, not the entire page. Don't include any additional comments either. Do not respond to me with anything other than html. Here is a description for the component: {description}.",
        examples=(
            "Write an empty div",
            "```<div></div>```",
            'Write a span with "1" in it',
            "```<span>1</span>```",
        ),
    )
    if "```" in component:
        component = partition_text_inside_backticks(component)
        component = component.replace("html", "")
    return component


@router.get("/{example}", response_class=HTMLResponse, summary="Example")
async def get_example(request: Request, example: str):
    with open("project/data/components.json") as f:
        components = json.load(f)
    component_data = components.get(example)
    if component_data is None:
        raise HTTPException(status_code=404, detail="Example not found")
    if component_data.get("markup", "") == "":
        markup = generate_component_html(example, component_data.get("description"))
        components[example]["markup"] = markup
        with open("project/data/components.json", "w") as f:
            json.dump(components, f, indent=4)
    return templates.TemplateResponse(
        f"examples/display.jinja",
        {
            "request": request,
            "component": component_data.get("markup"),
            "component_data": component_data,
        },
    )

This modified my baked data like so:

    {
    "card": {
        "title": "Card",
        "description": "A flexible container used to present information or media, often used to display content in a visually appealing manner.",
        "markup": "\n<div class=\"max-w-sm mx-auto bg-white shadow-lg rounded-lg overflow-hidden\">\n  <div class=\"sm:flex sm:items-center px-6 py-4\">\n    <img class=\"block h-16 sm:h-24 rounded-full mx-auto mb-4 sm:mb-0 sm:mr-4 sm:ml-0\" src=\"image.jpg\" alt=\"\">\n    <div class=\"text-center sm:text-left sm:flex-grow\">\n      <div class=\"mb-4\">\n        <p class=\"text-xl leading-tight\">Title</p>\n        <p class=\"text-sm leading-tight text-grey-dark\">Sub-title</p>\n      </div>\n      <div>\n        <button class=\"text-xs font-semibold rounded-full px-4 py-1 leading-normal bg-white border border-purple text-purple hover:bg-purple hover:text-white\">Button</button>\n      </div>\n    </div>\n  </div>\n</div>\n"
    }
 }

With the data generated, all that was left to do was render it all. With the help of GPT I was able to ask it to write a few web pages.

The component view:

The introduction page:

The blog page (featuring PinesUI components):

During this process, I also asked GPT to generate a few blog posts for me for SEO purposes:

A blog post auto-generated using GPT-4

Hours 4-6 - Finishing Up

With the core of the website set up, the were just a few administrative tasks remaining - getting the site hosted on Railway (referral link) being the first.

I followed up by purchasing a domain name, setting up the site with Google Ads, and generating a few more blog posts.

Conclusion

Overall, this process of developing a new website was definitely accelerated with GPT. I would not have been able to implement so many distinct components in such a reasonable amount of time, especially for the trickier ones that required AlpineJS (I'm still a JS novice). Being able to see something like a context menu just work on the first try was super motivating!

As of today, Google Ads says I've earned a grand total of $0.05 from this website.

Well, at least I had fun.

Subscribe to Thorne Wolfenbarger - Blog

Don’t miss out on the latest issues. Sign up now to get access to the library of members-only issues.
jamie@example.com
Subscribe