The front end of SAFE Stack applications is essentially just a React application, using F# to compose together React components and associated logic. Out of the box (at the time of writing) SAFE Stack applications come bundled with the Bulma CSS framework. It also comes with an F# "wrapper" around Bulma, Feliz.Bulma (and previously Fulma). These provide a strongly-typed DSL that emit React components with the appropriate styles.
For example, the following Feliz.Bulma code emits JS that React converts into the associated HTML.
open Feliz.Bulma
Bulma.columns [
Bulma.column [
column.is2 // <-- note context helper here
prop.children [
Bulma.button.button "Click me"
]
]
]
<div class="columns">
<div class="column is-2">
<a class="button" text="click me">
</a>
</div>
</div>
Feliz.Bulma or Fulma?
It's important to understand that both Fulma and Feliz.Bulma emit the exact same JS code and both are wrappers around the same Bulma. The difference is the design of the API that both expose.
- Feliz.Bulma follows the Feliz-style API; Fulma follows the "original" Fable.React style. The key differences are the way in which you apply styles and child elements.
- Fulma is slightly more type-safe as each component e.g. button or textbox have their own type and style, which means that you cannot accidentally apply a button prop to a textbox.
You can happily mix-and-match DSLs from a type system point of view - you can use other Feliz libraries along with Fulma, for example - it's just an API design pattern.
How do I create my own wrapper?
It's important to understand what libraries like Feliz.Bulma are really doing. The above Feliz.Bulma code is simply a set of functions that emits the following:
Html.div [
prop.className "columns"
prop.children [
Html.div [
prop.className "column is-2"
prop.children [
Html.button.a [
prop.className "button"
prop.text "Click me"
]
]
]
]
]
These all "standard" out-of-the-box Feliz.React types and values. As you can see, the only "customised" code is the className
prop i.e. the CSS styles. That means that you can directly start to create components that have the look-and-feel of a style library without relying on specific wrappers. You can also start to create your own "custom" wrapper for your specific application e.g.:
module MyAppUi =
let button (text:string) onClick =
Html.button.a [
prop.className "button"
// any other common styling that you want all buttons to have...
prop.text text
prop.onClick onClick
]
// ....
MyAppUi.button "myButton" (fun _ -> dispatch SaveData)
In this example, I have simply made a function button
which takes in the text and an on-click handler. The only dependency that would be required in this case would be the css / JS packages. In this way, you can start to create a basic "style library" that is specific to your application.
Summary
CSS frameworks add a consistent look-and-feel to your application. It is often desirable to use type-safe F# wrappers around them; however, you can quickly create your own functions that wrap around CSS frameworks without necessarily relying upon authors to create full, flexible F# library wrappers.