base on CSS written in Pure Go [![Test](https://github.com/AccentDesign/gcss/actions/workflows/go-test.yml/badge.svg)](https://github.com/AccentDesign/gcss/actions/workflows/go-test.yml) [![Go Report Card](https://goreportcard.com/badge/github.com/AccentDesign/gcss)](https://goreportcard.com/report/github.com/AccentDesign/gcss) <a href="https://pkg.go.dev/github.com/AccentDesign/gcss"><img src="https://img.shields.io/badge/Documentation%20on-pkg.go.dev-blue.svg"/></a> <img src="banner.jpg" alt="banner" style="width: 100%; height: auto;"> # gcss CSS written in Pure Go. ## Motivation This is really just a bit of fun and a way to write CSS in Go. I wanted to see if it was possible and what would it look like. I wanted to find a way to easily control the CSS from the server side and not have to worry about pre-building the css to take variables and stuff. I didnt want to use UI libraries that are written for JS frameworks and I didn't want to use preprocessors or linters that add more steps to the build process. Could I just use CSS? Yes of course and I will, but I wanted to see if I could write CSS in Go as this is what is compiling the rest of the project. ## Gopher No it looks nothing like the Go gopher, but it's a gopher and I like it. It's the best I could get from the LM without giving up, [ideogram.ai (1400097641)](https://ideogram.ai/g/E-5MQp7QTPO4uyF9PvERzw/3). ## Next steps The next steps for this project are to add more features to the CSS package. This includes adding support for more CSS properties when the need arises. What I don't want to do is to add support for all CSS functionality as some things are better in CSS, but I do want to be able to create a few UI components that are configurable using Go. ## Installation ```bash go get github.com/AccentDesign/gcss ``` ## Quickstart There is a separate repo with the full example [here](https://github.com/AccentDesign/gcss-starter) which will evolve over time. ```bash git clone https://github.com/AccentDesign/gcss-starter.git ``` install the dependencies: ```bash # for hot reloading go install github.com/cosmtrek/air@latest ``` ```bash go mod tidy ``` run the server: ```bash air ``` ## Usage ### Basic usage `gcss` defines a `Style` type that can be used to hold the properties for a specific css selector, eg: ```go style := gcss.Style{ Selector: "body", Props: gcss.Props{ BackgroundColor: props.ColorRGBA(0, 0, 0, 128), }, } ``` The `CSS` function on the `Style` is used to write the style to a `io.Writer`: ```go style.CSS(os.Stdout) ``` which gives you: ```css body{background-color:rgba(0,0,0,0.50);} ``` That's all there is to it. But it's not very useful on it's own I hear you say. ### Multiple styles Well you can then use that to define a `Styles` type that can be used to hold multiple `Style` types: ```go type Styles []gcss.Style func (s Styles) CSS(w io.Writer) error { // handle your errors for _, style := range s { style.CSS(w) } return nil } styles := Styles{ { Selector: "body", Props: gcss.Props{ BackgroundColor: props.ColorRGBA(0, 0, 0, 128), }, }, { Selector: "main", Props: gcss.Props{ Padding: props.UnitRem(8.5), }, }, } styles.CSS(os.Stdout) ``` which gives you: ```css /* formatted for visibility */ body{ background-color:rgba(0,0,0,0.50); } main{ padding:8.500rem; } ``` ### Need a bit more? what about a dark and light theme? keep the last example in mind and read on. Define a `Theme` type that can be used to hold attributes for a specific theme, eg: ```go type Theme struct { MediaQuery string Background props.Color } func (t *Theme) CSS(w io.Writer) error { // handle your errors fmt.Fprintf(w, "%s{", t.MediaQuery) for _, style := range t.Styles() { style.CSS(w) } fmt.Fprint(w, "}") } // Styles returns the styles for the theme. // Can be any number of styles you want and any number of functions // you just need them in the CSS function to loop over. func (t *Theme) Styles() Styles { return Styles{ { Selector: "body", Props: gcss.Props{ BackgroundColor: t.Background, }, }, } } ``` Then you can define a `Stylesheet` type that can be used to hold multiple `Theme` types: ```go type Stylesheet struct { Dark *Theme Light *Theme } func (s *Stylesheet) CSS(w io.Writer) error { // handle your errors s.Dark.CSS(w) s.Light.CSS(w) return nil } ``` Finally, you can use the `Stylesheet` type to write the css to a `io.Writer`: ```go styles := Stylesheet{ Dark: &Theme{ MediaQuery: "@media (prefers-color-scheme: dark)", Background: props.ColorRGBA(0, 0, 0, 255), }, Light: &Theme{ MediaQuery: "@media (prefers-color-scheme: light)", Background: props.ColorRGBA(255, 255, 255, 255), }, } styles.CSS(os.Stdout) ``` gives you: ```css /* formatted for visibility */ @media (prefers-color-scheme: dark) { body{ background-color:rgba(0,0,0,1.00); } } @media (prefers-color-scheme: light) { body{ background-color:rgba(255,255,255,1.00); } } ``` Hopefully this will get you going. The rest is up to you. * Maybe create a button function that takes a `props.Color` and returns a Style. * Or add extra `Styles` to the `Stylesheet` to additionally include non themed styles. * It's all about how you construct the `Stylesheet` and use the `gcss.Style` type. * If I could have created a `Stylesheet` type that fits well any use case at all I would have, but there is a world of possibility, so I left it up to you. ## The benefits * Total control of the CSS from the server side. * CSS doesn't have mixins, but you can create a function that returns a `Style` type and reuse it. * Keeps the css free of variables. * Keeps html free of classes like `bg-gray-50 text-black dark:bg-slate-800 dark:text-white` and eliminates the need to remember to add the dark variant. * I recently saw a button component on an html page 10 times with over 1800 characters in the class attribute of each. This is not maintainable nor debuggable. * Keeps the css clean and easy to debug with no overrides like the above. ## Examples For example usage see the [examples](./examples) directory that include: * [Full example](./examples/full) - A full example including base, media and themed styles with a `sync.Mutex` for caching the css in a `http.HandleFunc`. * [CSS resets](./examples/css-resets) - A simple example collection of css resets. * [Templ integration](./examples/integration-templ) - An example of how to load styles from gcss with the [templ](https://templ.guide) package. * [Media queries](./examples/media-queries) - An example of how to use media queries. * [Template function](./examples/template-function) - An example of how to write css to the template directly via `template.FuncMap` and `template.CSS`. * [Write to a file](./examples/to-file) - An example of how to write to a file. * [Write to an HTTP handler](./examples/to-http-handler) - An example of how to write to an http handler. * [Write to stdout](./examples/to-stdout) - An example of how to write to stdout. ## Contributing If you would like to contribute to this project, please open an issue or a pull request. We welcome all contributions and ideas. ## Mix it up with other CSS frameworks You can mix `gcss` with other CSS frameworks like `tailwindcss` for example: separate the css files into base and utils: ```css /* base.css */ @tailwind base; ``` ```css /* utils.css */ @tailwind utilities; ``` Then add the `gcss` styles in between in your html: ```html <link rel="stylesheet" href="base.css"> <link rel="stylesheet" href="gcss-styles.css"> <link rel="stylesheet" href="utils.css"> ``` Try to keep the specificity of the `gcss` styles to 1 by using single classes this will ensure any `tailwindcss` utilities will be able to overwrite your styles where required. ", Assign "at most 3 tags" to the expected json: {"id":"10600","tags":[]} "only from the tags list I provide: [{"id":77,"name":"3d"},{"id":89,"name":"agent"},{"id":17,"name":"ai"},{"id":54,"name":"algorithm"},{"id":24,"name":"api"},{"id":44,"name":"authentication"},{"id":3,"name":"aws"},{"id":27,"name":"backend"},{"id":60,"name":"benchmark"},{"id":72,"name":"best-practices"},{"id":39,"name":"bitcoin"},{"id":37,"name":"blockchain"},{"id":1,"name":"blog"},{"id":45,"name":"bundler"},{"id":58,"name":"cache"},{"id":21,"name":"chat"},{"id":49,"name":"cicd"},{"id":4,"name":"cli"},{"id":64,"name":"cloud-native"},{"id":48,"name":"cms"},{"id":61,"name":"compiler"},{"id":68,"name":"containerization"},{"id":92,"name":"crm"},{"id":34,"name":"data"},{"id":47,"name":"database"},{"id":8,"name":"declarative-gui "},{"id":9,"name":"deploy-tool"},{"id":53,"name":"desktop-app"},{"id":6,"name":"dev-exp-lib"},{"id":59,"name":"dev-tool"},{"id":13,"name":"ecommerce"},{"id":26,"name":"editor"},{"id":66,"name":"emulator"},{"id":62,"name":"filesystem"},{"id":80,"name":"finance"},{"id":15,"name":"firmware"},{"id":73,"name":"for-fun"},{"id":2,"name":"framework"},{"id":11,"name":"frontend"},{"id":22,"name":"game"},{"id":81,"name":"game-engine "},{"id":23,"name":"graphql"},{"id":84,"name":"gui"},{"id":91,"name":"http"},{"id":5,"name":"http-client"},{"id":51,"name":"iac"},{"id":30,"name":"ide"},{"id":78,"name":"iot"},{"id":40,"name":"json"},{"id":83,"name":"julian"},{"id":38,"name":"k8s"},{"id":31,"name":"language"},{"id":10,"name":"learning-resource"},{"id":33,"name":"lib"},{"id":41,"name":"linter"},{"id":28,"name":"lms"},{"id":16,"name":"logging"},{"id":76,"name":"low-code"},{"id":90,"name":"message-queue"},{"id":42,"name":"mobile-app"},{"id":18,"name":"monitoring"},{"id":36,"name":"networking"},{"id":7,"name":"node-version"},{"id":55,"name":"nosql"},{"id":57,"name":"observability"},{"id":46,"name":"orm"},{"id":52,"name":"os"},{"id":14,"name":"parser"},{"id":74,"name":"react"},{"id":82,"name":"real-time"},{"id":56,"name":"robot"},{"id":65,"name":"runtime"},{"id":32,"name":"sdk"},{"id":71,"name":"search"},{"id":63,"name":"secrets"},{"id":25,"name":"security"},{"id":85,"name":"server"},{"id":86,"name":"serverless"},{"id":70,"name":"storage"},{"id":75,"name":"system-design"},{"id":79,"name":"terminal"},{"id":29,"name":"testing"},{"id":12,"name":"ui"},{"id":50,"name":"ux"},{"id":88,"name":"video"},{"id":20,"name":"web-app"},{"id":35,"name":"web-server"},{"id":43,"name":"webassembly"},{"id":69,"name":"workflow"},{"id":87,"name":"yaml"}]" returns me the "expected json"