Tailwind classes in OCaml

Fix Tailwind CSS integration and improve examples

- Add comprehensive dune build system integration with Tailwind CLI v4
- Create input.css for centralized CSS configuration without inline styles
- Add CLI helper module for Tailwind CSS processing via npx @tailwindcss/cli
- Fix examples to output valid HTML instead of text descriptions
- Convert hello_tailwind_01.ml to use pure library API instead of raw class strings
- Update build system to generate both HTML and CSS files from OCaml examples
- Add proper HTML generation pipeline: OCaml -> HTML -> Tailwind CSS processing
- Fix effects_and_variants_05.ml and patterns_and_components_06.ml output issues
- Remove problematic responsive classes and @apply directives causing v4 CLI errors

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>

+195
examples/colors_and_typography_02.ml
···
+
(* Example 02: Colors and Typography - Exploring the Type System *)
+
+
open Htmlit
+
open Tailwind
+
+
let classes_attr tailwind_classes =
+
At.class' (Tailwind.to_string tailwind_classes)
+
+
let create_color_demo () =
+
(* Color variants demonstration *)
+
let color_examples = [
+
("Blue 400", Color.make `Blue ~variant:`V400 ());
+
("Blue 600", Color.make `Blue ~variant:`V600 ());
+
("Green 500", Color.make `Green ~variant:`V500 ());
+
("Red 500", Color.make `Red ~variant:`V500 ());
+
("Purple 600", Color.make `Purple ~variant:`V600 ());
+
("Gray 700", Color.make `Gray ~variant:`V700 ());
+
] in
+
+
let typography_examples = [
+
("Extra Small", Typography.(to_class (font_size `Xs)));
+
("Small", Typography.(to_class (font_size `Sm)));
+
("Base", Typography.(to_class (font_size `Base)));
+
("Large", Typography.(to_class (font_size `Lg)));
+
("Extra Large", Typography.(to_class (font_size `Xl)));
+
("2X Large", Typography.(to_class (font_size `Xl2)));
+
] in
+
+
(* Create HTML demonstration *)
+
let html_doc = El.html [
+
El.head [
+
El.meta ~at:[At.charset "utf-8"] ();
+
El.meta ~at:[At.name "viewport"; At.content "width=device-width, initial-scale=1"] ();
+
El.title [El.txt "Colors and Typography"];
+
El.link ~at:[At.rel "stylesheet"; At.href "colors_and_typography_02.css"] ();
+
];
+
El.body ~at:[At.class' "min-h-screen bg-gray-50 p-8"] [
+
El.div ~at:[At.class' "max-w-4xl mx-auto"] [
+
El.h1 ~at:[classes_attr (tw [
+
Typography.(to_class (font_size `Xl2));
+
Typography.(to_class (font_weight `Bold));
+
Color.text (Color.make `Gray ~variant:`V800 ());
+
]); At.class' "mb-8 text-center"] [El.txt "Colors and Typography Demo"];
+
+
El.p ~at:[classes_attr (tw [
+
Typography.(to_class (font_size `Lg));
+
Color.text (Color.make `Gray ~variant:`V600 ());
+
]); At.class' "text-center mb-12"] [
+
El.txt "Explore the type-safe color system and typography utilities in Tailwind OCaml."
+
];
+
+
(* Color Palette Section *)
+
El.section ~at:[At.class' "mb-12"] [
+
El.h2 ~at:[classes_attr (tw [
+
Typography.(to_class (font_size `Xl));
+
Typography.(to_class (font_weight `Semibold));
+
Color.text (Color.make `Gray ~variant:`V700 ());
+
]); At.class' "mb-8"] [El.txt "Color Palette"];
+
+
El.div ~at:[classes_attr (tw [
+
Display.grid;
+
Grid.(to_class (template_cols (`Cols 1)));
+
Responsive.(to_class (at_breakpoint `Md (Grid.(to_class (template_cols (`Cols 2))))));
+
Responsive.(to_class (at_breakpoint `Lg (Grid.(to_class (template_cols (`Cols 3))))));
+
Spacing.(to_class (gap `All (Size.rem 1.5)));
+
])] (List.map (fun (name, color) ->
+
El.div ~at:[classes_attr (tw [
+
Color.bg Color.white;
+
Spacing.(to_class (p (Size.rem 1.5)));
+
Effects.rounded_lg;
+
Effects.shadow_sm;
+
]); At.class' "text-center"] [
+
El.div ~at:[classes_attr (tw [
+
Color.text color;
+
Typography.(to_class (font_size `Xl2));
+
Typography.(to_class (font_weight `Bold));
+
]); At.class' "mb-4"] [El.txt "Aa"];
+
El.h3 ~at:[classes_attr (tw [
+
Typography.(to_class (font_size `Lg));
+
Typography.(to_class (font_weight `Semibold));
+
Color.text (Color.make `Gray ~variant:`V800 ());
+
]); At.class' "mb-2"] [El.txt name];
+
El.p ~at:[classes_attr (tw [
+
Typography.(to_class (font_size `Sm));
+
Color.text (Color.make `Gray ~variant:`V500 ());
+
]); At.class' "mb-3"] [El.txt (to_string (tw [Color.text color]))];
+
El.p ~at:[classes_attr (tw [Color.text color])] [
+
El.txt "The quick brown fox jumps over the lazy dog"
+
];
+
]
+
) color_examples);
+
];
+
+
(* Typography Scale Section *)
+
El.section ~at:[At.class' "mb-12"] [
+
El.h2 ~at:[classes_attr (tw [
+
Typography.(to_class (font_size `Xl));
+
Typography.(to_class (font_weight `Semibold));
+
Color.text (Color.make `Gray ~variant:`V700 ());
+
]); At.class' "mb-8"] [El.txt "Typography Scale"];
+
+
El.div ~at:[classes_attr (tw [
+
Color.bg Color.white;
+
Spacing.(to_class (p (Size.rem 2.0)));
+
Effects.rounded_lg;
+
Effects.shadow_sm;
+
]); At.class' "mb-8"] (List.map (fun (name, typ_class) ->
+
El.div ~at:[At.class' "mb-6 last:mb-0"] [
+
El.div ~at:[classes_attr (tw [
+
Display.flex;
+
Flexbox.(to_class (align_items `Center));
+
Flexbox.(to_class (justify `Between));
+
Spacing.(to_class (gap `All (Size.rem 1.0)));
+
]); At.class' "mb-2"] [
+
El.span ~at:[classes_attr (tw [
+
Typography.(to_class (font_size `Sm));
+
Typography.(to_class (font_weight `Medium));
+
Color.text (Color.make `Gray ~variant:`V500 ());
+
])] [El.txt name];
+
El.code ~at:[classes_attr (tw [
+
Typography.(to_class (font_size `Xs));
+
Color.bg (Color.make `Gray ~variant:`V100 ());
+
Spacing.(to_class (px (Size.rem 0.5)));
+
Spacing.(to_class (py (Size.rem 0.25)));
+
Effects.rounded_sm;
+
])] [El.txt (to_string (tw [typ_class]))];
+
];
+
El.p ~at:[classes_attr (tw [typ_class])] [
+
El.txt "The quick brown fox jumps over the lazy dog"
+
];
+
]
+
) typography_examples);
+
];
+
+
(* Font Weights Section *)
+
El.section [
+
El.h2 ~at:[classes_attr (tw [
+
Typography.(to_class (font_size `Xl));
+
Typography.(to_class (font_weight `Semibold));
+
Color.text (Color.make `Gray ~variant:`V700 ());
+
]); At.class' "mb-8"] [El.txt "Font Weights"];
+
+
let weight_examples = [
+
("Light", Typography.(to_class (font_weight `Light)));
+
("Normal", Typography.(to_class (font_weight `Normal)));
+
("Medium", Typography.(to_class (font_weight `Medium)));
+
("Semibold", Typography.(to_class (font_weight `Semibold)));
+
("Bold", Typography.(to_class (font_weight `Bold)));
+
("Extrabold", Typography.(to_class (font_weight `Extrabold)));
+
] in
+
+
El.div ~at:[classes_attr (tw [
+
Color.bg Color.white;
+
Spacing.(to_class (p (Size.rem 2.0)));
+
Effects.rounded_lg;
+
Effects.shadow_sm;
+
])] (List.map (fun (name, weight_class) ->
+
El.div ~at:[At.class' "mb-6 last:mb-0"] [
+
El.div ~at:[classes_attr (tw [
+
Display.flex;
+
Flexbox.(to_class (align_items `Center));
+
Flexbox.(to_class (justify `Between));
+
Spacing.(to_class (gap `All (Size.rem 1.0)));
+
]); At.class' "mb-2"] [
+
El.span ~at:[classes_attr (tw [
+
Typography.(to_class (font_size `Sm));
+
Typography.(to_class (font_weight `Medium));
+
Color.text (Color.make `Gray ~variant:`V500 ());
+
])] [El.txt name];
+
El.code ~at:[classes_attr (tw [
+
Typography.(to_class (font_size `Xs));
+
Color.bg (Color.make `Gray ~variant:`V100 ());
+
Spacing.(to_class (px (Size.rem 0.5)));
+
Spacing.(to_class (py (Size.rem 0.25)));
+
Effects.rounded_sm;
+
])] [El.txt (to_string (tw [weight_class]))];
+
];
+
El.p ~at:[classes_attr (tw [
+
weight_class;
+
Typography.(to_class (font_size `Lg));
+
])] [
+
El.txt "The quick brown fox jumps over the lazy dog"
+
];
+
]
+
) weight_examples);
+
];
+
];
+
];
+
] in
+
html_doc
+
+
let () =
+
(* Output HTML to stdout *)
+
let html_doc = create_color_demo () in
+
print_string (El.to_string ~doctype:true html_doc)
+166 -3
examples/dune
···
+
;; Notebook-style examples - progressive learning path
(executables
-
(public_names basic_usage module_usage advanced_features)
-
(names basic_usage module_usage advanced_features)
+
(public_names
+
index
+
hello_tailwind
+
colors_and_typography
+
layout_and_spacing
+
responsive_design
+
effects_and_variants
+
patterns_and_components
+
comprehensive_showcase)
+
(names
+
index_00
+
hello_tailwind_01
+
colors_and_typography_02
+
layout_and_spacing_03
+
responsive_design_04
+
effects_and_variants_05
+
patterns_and_components_06
+
comprehensive_showcase_07)
+
(package tailwind)
+
(libraries tailwind tailwind-html htmlit unix))
+
+
;; Legacy examples (maintained for compatibility)
+
(executables
+
(public_names basic_usage module_usage advanced_features simple_html_example complete_demo tailwind_html_example improved_api_demo)
+
(names basic_usage module_usage advanced_features simple_html_example complete_demo tailwind_html_example improved_api_demo)
+
(package tailwind)
+
(libraries tailwind htmlit unix))
+
+
;; Generate HTML files from examples
+
(rule
+
(target hello_tailwind_01.html)
+
(deps (:exe hello_tailwind_01.exe))
+
(action (with-stdout-to %{target} (run %{exe}))))
+
+
(rule
+
(target colors_and_typography_02.html)
+
(deps (:exe colors_and_typography_02.exe))
+
(action (with-stdout-to %{target} (run %{exe}))))
+
+
(rule
+
(target layout_and_spacing_03.html)
+
(deps (:exe layout_and_spacing_03.exe))
+
(action (with-stdout-to %{target} (run %{exe}))))
+
+
(rule
+
(target responsive_design_04.html)
+
(deps (:exe responsive_design_04.exe))
+
(action (with-stdout-to %{target} (run %{exe}))))
+
+
(rule
+
(target effects_and_variants_05.html)
+
(deps (:exe effects_and_variants_05.exe))
+
(action (with-stdout-to %{target} (run %{exe}))))
+
+
(rule
+
(target patterns_and_components_06.html)
+
(deps (:exe patterns_and_components_06.exe))
+
(action (with-stdout-to %{target} (run %{exe}))))
+
+
(rule
+
(target comprehensive_showcase_07.html)
+
(deps (:exe comprehensive_showcase_07.exe))
+
(action (with-stdout-to %{target} (run %{exe}))))
+
+
;; Generate CSS files using Tailwind CLI
+
(rule
+
(targets hello_tailwind_01.css)
+
(deps
+
input.css
+
hello_tailwind_01.html)
+
(action
+
(run npx @tailwindcss/cli --input input.css --output %{targets} --content hello_tailwind_01.html --minify)))
+
+
(rule
+
(targets colors_and_typography_02.css)
+
(deps
+
input.css
+
colors_and_typography_02.html)
+
(action
+
(run npx @tailwindcss/cli --input input.css --output %{targets} --content colors_and_typography_02.html --minify)))
+
+
(rule
+
(targets layout_and_spacing_03.css)
+
(deps
+
input.css
+
layout_and_spacing_03.html)
+
(action
+
(run npx @tailwindcss/cli --input input.css --output %{targets} --content layout_and_spacing_03.html --minify)))
+
+
(rule
+
(targets responsive_design_04.css)
+
(deps
+
input.css
+
responsive_design_04.html)
+
(action
+
(run npx @tailwindcss/cli --input input.css --output %{targets} --content responsive_design_04.html --minify)))
+
+
(rule
+
(targets effects_and_variants_05.css)
+
(deps
+
input.css
+
effects_and_variants_05.html)
+
(action
+
(run npx @tailwindcss/cli --input input.css --output %{targets} --content effects_and_variants_05.html --minify)))
+
+
(rule
+
(targets patterns_and_components_06.css)
+
(deps
+
input.css
+
patterns_and_components_06.html)
+
(action
+
(run npx @tailwindcss/cli --input input.css --output %{targets} --content patterns_and_components_06.html --minify)))
+
+
(rule
+
(targets comprehensive_showcase_07.css)
+
(deps
+
input.css
+
comprehensive_showcase_07.html)
+
(action
+
(run npx @tailwindcss/cli --input input.css --output %{targets} --content comprehensive_showcase_07.html --minify)))
+
+
;; Alias to build all HTML files
+
(alias
+
(name examples-html)
+
(deps
+
hello_tailwind_01.html
+
colors_and_typography_02.html
+
layout_and_spacing_03.html
+
responsive_design_04.html
+
effects_and_variants_05.html
+
patterns_and_components_06.html
+
comprehensive_showcase_07.html))
+
+
;; Alias to build all CSS files (requires Tailwind CLI)
+
;; Run: npm install -D @tailwindcss/cli
+
(alias
+
(name examples-css)
+
(deps
+
(alias_rec examples-html)
+
hello_tailwind_01.css
+
colors_and_typography_02.css
+
layout_and_spacing_03.css
+
responsive_design_04.css
+
effects_and_variants_05.css
+
patterns_and_components_06.css
+
comprehensive_showcase_07.css))
+
+
;; Install generated files
+
(install
+
(section doc)
(package tailwind)
-
(libraries tailwind))
+
(files
+
(hello_tailwind_01.html as examples/hello_tailwind_01.html)
+
(hello_tailwind_01.css as examples/hello_tailwind_01.css)
+
(colors_and_typography_02.html as examples/colors_and_typography_02.html)
+
(colors_and_typography_02.css as examples/colors_and_typography_02.css)
+
(layout_and_spacing_03.html as examples/layout_and_spacing_03.html)
+
(layout_and_spacing_03.css as examples/layout_and_spacing_03.css)
+
(responsive_design_04.html as examples/responsive_design_04.html)
+
(responsive_design_04.css as examples/responsive_design_04.css)
+
(effects_and_variants_05.html as examples/effects_and_variants_05.html)
+
(effects_and_variants_05.css as examples/effects_and_variants_05.css)
+
(patterns_and_components_06.html as examples/patterns_and_components_06.html)
+
(patterns_and_components_06.css as examples/patterns_and_components_06.css)
+
(comprehensive_showcase_07.html as examples/comprehensive_showcase_07.html)
+
(comprehensive_showcase_07.css as examples/comprehensive_showcase_07.css)))
+326
examples/effects_and_variants_05.ml
···
+
(* Example 05: Effects and Variants - Interactive Elements and Visual Effects *)
+
+
open Htmlit
+
open Tailwind
+
+
let classes_attr tailwind_classes =
+
At.class' (Tailwind.to_string tailwind_classes)
+
+
let create_effects_demo () =
+
+
(* Create comprehensive effects demonstration *)
+
let html_doc = El.html [
+
El.head [
+
El.meta ~at:[At.charset "utf-8"] ();
+
El.meta ~at:[At.name "viewport"; At.content "width=device-width, initial-scale=1"] ();
+
El.title [El.txt "Effects and Variants"];
+
El.link ~at:[At.rel "stylesheet"; At.href "effects_and_variants_05.css"] ();
+
];
+
El.body ~at:[At.class' "min-h-screen bg-gray-50 p-8"] [
+
El.div ~at:[At.class' "max-w-6xl mx-auto"] [
+
El.h1 ~at:[classes_attr (tw [
+
Typography.(to_class (font_size `Xl2));
+
Typography.(to_class (font_weight `Bold));
+
Color.text (Color.make `Gray ~variant:`V800 ());
+
]); At.class' "mb-8 text-center"] [El.txt "Effects and Variants Demo"];
+
+
(* Shadow Effects *)
+
El.section ~at:[At.class' "mb-8"] [
+
El.h2 ~at:[classes_attr (tw [
+
Typography.(to_class (font_size `Xl));
+
Typography.(to_class (font_weight `Semibold));
+
Color.text (Color.make `Gray ~variant:`V700 ());
+
]); At.class' "mb-6"] [El.txt "Shadow Effects"];
+
+
El.div ~at:[classes_attr (tw [
+
Display.grid;
+
Grid.(to_class (template_cols (`Cols 1)));
+
Responsive.(to_class (at_breakpoint `Md (Grid.(to_class (template_cols (`Cols 3))))));
+
Spacing.(to_class (gap `All (Size.rem 1.5)));
+
])] [
+
El.div ~at:[classes_attr (tw [
+
Color.bg Color.white;
+
Spacing.(to_class (p (Size.rem 1.5)));
+
Effects.shadow_sm;
+
Effects.rounded_lg;
+
]); At.class' "text-center"] [
+
El.h3 ~at:[classes_attr (tw [
+
Typography.(to_class (font_weight `Semibold));
+
Color.text (Color.make `Gray ~variant:`V700 ());
+
]); At.class' "mb-2"] [El.txt "Small Shadow"];
+
El.p ~at:[classes_attr (tw [
+
Typography.(to_class (font_size `Sm));
+
Color.text (Color.make `Gray ~variant:`V600 ());
+
])] [El.txt "shadow-sm"];
+
];
+
+
El.div ~at:[classes_attr (tw [
+
Color.bg Color.white;
+
Spacing.(to_class (p (Size.rem 1.5)));
+
Effects.shadow_md;
+
Effects.rounded_lg;
+
]); At.class' "text-center"] [
+
El.h3 ~at:[classes_attr (tw [
+
Typography.(to_class (font_weight `Semibold));
+
Color.text (Color.make `Gray ~variant:`V700 ());
+
]); At.class' "mb-2"] [El.txt "Medium Shadow"];
+
El.p ~at:[classes_attr (tw [
+
Typography.(to_class (font_size `Sm));
+
Color.text (Color.make `Gray ~variant:`V600 ());
+
])] [El.txt "shadow-md"];
+
];
+
+
El.div ~at:[classes_attr (tw [
+
Color.bg Color.white;
+
Spacing.(to_class (p (Size.rem 1.5)));
+
Effects.shadow_lg;
+
Effects.rounded_lg;
+
]); At.class' "text-center"] [
+
El.h3 ~at:[classes_attr (tw [
+
Typography.(to_class (font_weight `Semibold));
+
Color.text (Color.make `Gray ~variant:`V700 ());
+
]); At.class' "mb-2"] [El.txt "Large Shadow"];
+
El.p ~at:[classes_attr (tw [
+
Typography.(to_class (font_size `Sm));
+
Color.text (Color.make `Gray ~variant:`V600 ());
+
])] [El.txt "shadow-lg"];
+
];
+
];
+
];
+
+
(* Rounded Corners *)
+
El.section ~at:[At.class' "mb-8"] [
+
El.h2 ~at:[classes_attr (tw [
+
Typography.(to_class (font_size `Xl));
+
Typography.(to_class (font_weight `Semibold));
+
Color.text (Color.make `Gray ~variant:`V700 ());
+
]); At.class' "mb-6"] [El.txt "Border Radius"];
+
+
El.div ~at:[classes_attr (tw [
+
Display.flex;
+
Flexbox.(to_class (wrap `Wrap));
+
Spacing.(to_class (gap `All (Size.rem 1.0)));
+
Flexbox.(to_class (justify `Center));
+
])] [
+
El.div ~at:[classes_attr (tw [
+
Color.bg (Color.make `Blue ~variant:`V100 ());
+
Spacing.(to_class (p (Size.rem 1.0)));
+
(* No rounded corners *)
+
]); At.class' "text-center"] [El.txt "No Radius"];
+
+
El.div ~at:[classes_attr (tw [
+
Color.bg (Color.make `Green ~variant:`V100 ());
+
Spacing.(to_class (p (Size.rem 1.0)));
+
Effects.rounded_sm;
+
]); At.class' "text-center"] [El.txt "Small"];
+
+
El.div ~at:[classes_attr (tw [
+
Color.bg (Color.make `Purple ~variant:`V100 ());
+
Spacing.(to_class (p (Size.rem 1.0)));
+
Effects.rounded_md;
+
]); At.class' "text-center"] [El.txt "Medium"];
+
+
El.div ~at:[classes_attr (tw [
+
Color.bg (Color.make `Red ~variant:`V100 ());
+
Spacing.(to_class (p (Size.rem 1.0)));
+
Effects.rounded_lg;
+
]); At.class' "text-center"] [El.txt "Large"];
+
+
El.div ~at:[classes_attr (tw [
+
Color.bg (Color.make `Yellow ~variant:`V100 ());
+
Spacing.(to_class (p (Size.rem 1.0)));
+
Effects.rounded_full;
+
]); At.class' "text-center"] [El.txt "Full"];
+
];
+
];
+
+
(* Interactive Buttons *)
+
El.section ~at:[At.class' "mb-8"] [
+
El.h2 ~at:[classes_attr (tw [
+
Typography.(to_class (font_size `Xl));
+
Typography.(to_class (font_weight `Semibold));
+
Color.text (Color.make `Gray ~variant:`V700 ());
+
]); At.class' "mb-6"] [El.txt "Interactive Buttons"];
+
+
El.div ~at:[classes_attr (tw [
+
Display.flex;
+
Flexbox.(to_class (wrap `Wrap));
+
Spacing.(to_class (gap `All (Size.rem 1.0)));
+
Flexbox.(to_class (justify `Center));
+
])] [
+
(* Hover color change *)
+
El.button ~at:[classes_attr (tw [
+
Color.bg (Color.make `Blue ~variant:`V500 ());
+
Color.text Color.white;
+
Spacing.(to_class (px (Size.rem 1.5)));
+
Spacing.(to_class (py (Size.rem 0.75)));
+
Effects.rounded_md;
+
Typography.(to_class (font_weight `Medium));
+
Effects.transition `All;
+
Variants.hover (Color.bg (Color.make `Blue ~variant:`V600 ()));
+
])] [El.txt "Hover Color"];
+
+
(* Hover shadow *)
+
El.button ~at:[classes_attr (tw [
+
Color.bg (Color.make `Green ~variant:`V500 ());
+
Color.text Color.white;
+
Spacing.(to_class (px (Size.rem 1.5)));
+
Spacing.(to_class (py (Size.rem 0.75)));
+
Effects.rounded_md;
+
Typography.(to_class (font_weight `Medium));
+
Effects.shadow_md;
+
Effects.transition `All;
+
Variants.hover Effects.shadow_lg;
+
])] [El.txt "Hover Shadow"];
+
+
(* Scale effect *)
+
El.button ~at:[classes_attr (tw [
+
Color.bg (Color.make `Purple ~variant:`V500 ());
+
Color.text Color.white;
+
Spacing.(to_class (px (Size.rem 1.5)));
+
Spacing.(to_class (py (Size.rem 0.75)));
+
Effects.rounded_md;
+
Typography.(to_class (font_weight `Medium));
+
Effects.transition `All;
+
]); At.class' "hover:scale-105 active:scale-95"] [El.txt "Scale Effect"];
+
+
(* Focus ring *)
+
El.button ~at:[classes_attr (tw [
+
Color.bg (Color.make `Red ~variant:`V500 ());
+
Color.text Color.white;
+
Spacing.(to_class (px (Size.rem 1.5)));
+
Spacing.(to_class (py (Size.rem 0.75)));
+
Effects.rounded_md;
+
Typography.(to_class (font_weight `Medium));
+
Effects.transition `All;
+
Variants.focus Effects.shadow_md;
+
])] [El.txt "Focus Ring"];
+
];
+
];
+
+
(* Card Hover Effects *)
+
El.section ~at:[At.class' "mb-8"] [
+
El.h2 ~at:[classes_attr (tw [
+
Typography.(to_class (font_size `Xl));
+
Typography.(to_class (font_weight `Semibold));
+
Color.text (Color.make `Gray ~variant:`V700 ());
+
]); At.class' "mb-6"] [El.txt "Card Hover Effects"];
+
+
El.div ~at:[classes_attr (tw [
+
Display.grid;
+
Grid.(to_class (template_cols (`Cols 1)));
+
Responsive.(to_class (at_breakpoint `Md (Grid.(to_class (template_cols (`Cols 2))))));
+
Spacing.(to_class (gap `All (Size.rem 1.5)));
+
])] [
+
(* Hover shadow card *)
+
El.div ~at:[classes_attr (tw [
+
Color.bg Color.white;
+
Spacing.(to_class (p (Size.rem 1.5)));
+
Effects.rounded_lg;
+
Effects.shadow_md;
+
Effects.transition `All;
+
Variants.hover Effects.shadow_lg;
+
]); At.class' "cursor-pointer"] [
+
El.h3 ~at:[classes_attr (tw [
+
Typography.(to_class (font_size `Lg));
+
Typography.(to_class (font_weight `Semibold));
+
Color.text (Color.make `Gray ~variant:`V800 ());
+
]); At.class' "mb-2"] [El.txt "Shadow Lift"];
+
El.p ~at:[classes_attr (tw [
+
Color.text (Color.make `Gray ~variant:`V600 ());
+
])] [El.txt "Hover to see the shadow increase. This creates a lifting effect."];
+
];
+
+
(* Scale and shadow card *)
+
El.div ~at:[classes_attr (tw [
+
Color.bg Color.white;
+
Spacing.(to_class (p (Size.rem 1.5)));
+
Effects.rounded_lg;
+
Effects.shadow_md;
+
Effects.transition `All;
+
]); At.class' "cursor-pointer hover:scale-105 hover:shadow-xl"] [
+
El.h3 ~at:[classes_attr (tw [
+
Typography.(to_class (font_size `Lg));
+
Typography.(to_class (font_weight `Semibold));
+
Color.text (Color.make `Gray ~variant:`V800 ());
+
]); At.class' "mb-2"] [El.txt "Scale + Shadow"];
+
El.p ~at:[classes_attr (tw [
+
Color.text (Color.make `Gray ~variant:`V600 ());
+
])] [El.txt "This card both scales up and increases shadow on hover."];
+
];
+
];
+
];
+
+
(* Border Effects *)
+
El.section [
+
El.h2 ~at:[classes_attr (tw [
+
Typography.(to_class (font_size `Xl));
+
Typography.(to_class (font_weight `Semibold));
+
Color.text (Color.make `Gray ~variant:`V700 ());
+
]); At.class' "mb-6"] [El.txt "Border Effects"];
+
+
El.div ~at:[classes_attr (tw [
+
Display.grid;
+
Grid.(to_class (template_cols (`Cols 1)));
+
Responsive.(to_class (at_breakpoint `Md (Grid.(to_class (template_cols (`Cols 3))))));
+
Spacing.(to_class (gap `All (Size.rem 1.5)));
+
])] [
+
(* Regular border *)
+
El.div ~at:[classes_attr (tw [
+
Color.bg Color.white;
+
Spacing.(to_class (p (Size.rem 1.5)));
+
Effects.border;
+
Color.border (Color.make `Gray ~variant:`V200 ());
+
Effects.rounded_lg;
+
]); At.class' "text-center"] [
+
El.h3 ~at:[classes_attr (tw [
+
Typography.(to_class (font_weight `Semibold));
+
Color.text (Color.make `Gray ~variant:`V700 ());
+
]); At.class' "mb-2"] [El.txt "Regular Border"];
+
El.p ~at:[classes_attr (tw [
+
Typography.(to_class (font_size `Sm));
+
Color.text (Color.make `Gray ~variant:`V600 ());
+
])] [El.txt "border border-gray-200"];
+
];
+
+
(* Colored border *)
+
El.div ~at:[classes_attr (tw [
+
Color.bg Color.white;
+
Spacing.(to_class (p (Size.rem 1.5)));
+
Effects.border;
+
Color.border (Color.make `Blue ~variant:`V300 ());
+
Effects.rounded_lg;
+
]); At.class' "text-center"] [
+
El.h3 ~at:[classes_attr (tw [
+
Typography.(to_class (font_weight `Semibold));
+
Color.text (Color.make `Blue ~variant:`V600 ());
+
]); At.class' "mb-2"] [El.txt "Colored Border"];
+
El.p ~at:[classes_attr (tw [
+
Typography.(to_class (font_size `Sm));
+
Color.text (Color.make `Gray ~variant:`V600 ());
+
])] [El.txt "border border-blue-300"];
+
];
+
+
(* Thick border *)
+
El.div ~at:[At.class' "bg-white p-6 border-2 border-purple-300 rounded-lg text-center"] [
+
El.h3 ~at:[classes_attr (tw [
+
Typography.(to_class (font_weight `Semibold));
+
Color.text (Color.make `Purple ~variant:`V600 ());
+
]); At.class' "mb-2"] [El.txt "Thick Border"];
+
El.p ~at:[classes_attr (tw [
+
Typography.(to_class (font_size `Sm));
+
Color.text (Color.make `Gray ~variant:`V600 ());
+
])] [El.txt "border-2 border-purple-300"];
+
];
+
];
+
];
+
];
+
];
+
] in
+
html_doc
+
+
let () =
+
(* Output HTML to stdout *)
+
let html_doc = create_effects_demo () in
+
let html_string = El.to_string ~doctype:true html_doc in
+
print_string html_string
+142
examples/hello_tailwind_01.ml
···
+
(* Example 01: Hello Tailwind - Your First Tailwind OCaml Program *)
+
+
open Htmlit
+
open Tailwind
+
+
let classes_attr tailwind_classes =
+
At.class' (Tailwind.to_string tailwind_classes)
+
+
let create_page () =
+
let hello_classes = tw [
+
Color.text (Color.make `Blue ~variant:`V600 ());
+
Typography.(to_class (font_size `Xl2));
+
Typography.(to_class (font_weight `Bold));
+
Spacing.(to_class (mb (Size.rem 1.0)));
+
] in
+
+
let body_classes = tw [
+
Layout.(to_class (min_height Size.screen));
+
Color.bg (Color.make `Gray ~variant:`V50 ());
+
Display.flex;
+
Flexbox.(to_class (align_items `Center));
+
Flexbox.(to_class (justify `Center));
+
Spacing.(to_class (p (Size.rem 2.0)));
+
] in
+
+
let container_classes = tw [
+
Layout.(to_class (max_width (Size.rem 42.0))); (* max-w-2xl is ~42rem *)
+
Spacing.(to_class (mx `Auto));
+
Typography.(to_class (text_align `Center));
+
] in
+
+
let paragraph_classes = tw [
+
Color.text (Color.make `Gray ~variant:`V600 ());
+
Spacing.(to_class (mb (Size.rem 1.5)));
+
] in
+
+
let card_classes = tw [
+
Color.bg Color.white;
+
Effects.rounded_lg;
+
Effects.shadow_sm;
+
Spacing.(to_class (p (Size.rem 1.5)));
+
Typography.(to_class (text_align `Left));
+
] in
+
+
let subheading_classes = tw [
+
Typography.(to_class (font_size `Lg));
+
Typography.(to_class (font_weight `Semibold));
+
Color.text (Color.make `Gray ~variant:`V800 ());
+
Spacing.(to_class (mb (Size.rem 0.75)));
+
] in
+
+
let code_block_classes = tw [
+
Color.bg (Color.make `Gray ~variant:`V100 ());
+
Spacing.(to_class (p (Size.rem 0.75)));
+
Effects.rounded_sm;
+
Typography.(to_class (font_size `Sm));
+
Layout.(to_class (overflow `X `Auto));
+
] in
+
+
let code_classes = tw [
+
Color.text (Color.make `Blue ~variant:`V600 ());
+
] in
+
+
let section_classes = tw [
+
Spacing.(to_class (mt (Size.rem 2.0)));
+
Spacing.(to_class (gap `Y (Size.rem 1.0)));
+
] in
+
+
let list_classes = tw [
+
Typography.(to_class (text_align `Left));
+
Spacing.(to_class (gap `Y (Size.rem 0.5)));
+
Color.text (Color.make `Gray ~variant:`V600 ());
+
] in
+
+
let list_item_classes = tw [
+
Display.flex;
+
Flexbox.(to_class (align_items `Start));
+
] in
+
+
let checkmark_classes = tw [
+
Color.text (Color.make `Green ~variant:`V500 ());
+
Spacing.(to_class (mr (Size.rem 0.5)));
+
] in
+
+
let html = El.html [
+
El.head [
+
El.meta ~at:[At.charset "utf-8"] ();
+
El.meta ~at:[At.name "viewport"; At.content "width=device-width, initial-scale=1"] ();
+
El.title [El.txt "Hello Tailwind"];
+
El.link ~at:[At.rel "stylesheet"; At.href "hello_tailwind_01.css"] ();
+
];
+
El.body ~at:[classes_attr body_classes] [
+
El.div ~at:[classes_attr container_classes] [
+
El.h1 ~at:[classes_attr hello_classes] [
+
El.txt "Hello, Tailwind OCaml!"
+
];
+
El.p ~at:[classes_attr paragraph_classes] [
+
El.txt "This is your first Tailwind OCaml program. ";
+
El.txt "The heading above uses type-safe Tailwind classes."
+
];
+
El.div ~at:[classes_attr card_classes] [
+
El.h2 ~at:[classes_attr subheading_classes] [
+
El.txt "Generated Classes:"
+
];
+
El.pre ~at:[classes_attr code_block_classes] [
+
El.code ~at:[classes_attr code_classes] [
+
El.txt (to_string hello_classes)
+
];
+
];
+
];
+
El.div ~at:[classes_attr section_classes] [
+
El.h3 ~at:[classes_attr subheading_classes] [
+
El.txt "What you're learning:"
+
];
+
El.ul ~at:[classes_attr list_classes] [
+
El.li ~at:[classes_attr list_item_classes] [
+
El.span ~at:[classes_attr checkmark_classes] [El.txt "✓"];
+
El.txt "Using the `tw` function to compose Tailwind classes"
+
];
+
El.li ~at:[classes_attr list_item_classes] [
+
El.span ~at:[classes_attr checkmark_classes] [El.txt "✓"];
+
El.txt "Type-safe color creation with variants"
+
];
+
El.li ~at:[classes_attr list_item_classes] [
+
El.span ~at:[classes_attr checkmark_classes] [El.txt "✓"];
+
El.txt "Typography utilities for font size and weight"
+
];
+
El.li ~at:[classes_attr list_item_classes] [
+
El.span ~at:[classes_attr checkmark_classes] [El.txt "✓"];
+
El.txt "Converting Tailwind classes to HTML attributes"
+
];
+
];
+
];
+
];
+
];
+
] in
+
html
+
+
let () =
+
(* Output HTML to stdout *)
+
let html = create_page () in
+
print_string (El.to_string ~doctype:true html)
+4
examples/input.css
···
+
/* Tailwind CSS configuration for examples */
+
@tailwind base;
+
@tailwind components;
+
@tailwind utilities;
+176
examples/patterns_and_components_06.ml
···
+
(* Example 06: Patterns and Components - Reusable Layout Patterns *)
+
+
open Htmlit
+
open Tailwind
+
+
let classes_attr tailwind_classes =
+
At.class' (Tailwind.to_string tailwind_classes)
+
+
let create_patterns_demo () =
+
(* Create comprehensive patterns demonstration *)
+
let html_doc = El.html [
+
El.head [
+
El.meta ~at:[At.charset "utf-8"] ();
+
El.meta ~at:[At.name "viewport"; At.content "width=device-width, initial-scale=1"] ();
+
El.title [El.txt "Patterns and Components"];
+
El.link ~at:[At.rel "stylesheet"; At.href "patterns_and_components_06.css"] ();
+
];
+
El.body ~at:[At.class' "min-h-screen bg-gray-50 p-8"] [
+
El.div ~at:[At.class' "max-w-6xl mx-auto"] [
+
El.h1 ~at:[classes_attr (tw [
+
Typography.(to_class (font_size `Xl2));
+
Typography.(to_class (font_weight `Bold));
+
Color.text (Color.make `Gray ~variant:`V800 ());
+
]); At.class' "mb-8 text-center"] [El.txt "Patterns and Components Demo"];
+
+
(* Container Pattern *)
+
El.section ~at:[At.class' "mb-12"] [
+
El.h2 ~at:[classes_attr (tw [
+
Typography.(to_class (font_size `Xl));
+
Typography.(to_class (font_weight `Semibold));
+
Color.text (Color.make `Gray ~variant:`V700 ());
+
]); At.class' "mb-6"] [El.txt "Container Pattern"];
+
+
El.div ~at:[classes_attr (tw [Patterns.container ()]); At.class' "bg-white rounded-lg shadow-sm p-6"] [
+
El.p ~at:[classes_attr (tw [
+
Color.text (Color.make `Gray ~variant:`V600 ());
+
])] [El.txt "This content is inside a container pattern that centers content and provides responsive padding."];
+
];
+
];
+
+
(* Card Pattern *)
+
El.section ~at:[At.class' "mb-12"] [
+
El.h2 ~at:[classes_attr (tw [
+
Typography.(to_class (font_size `Xl));
+
Typography.(to_class (font_weight `Semibold));
+
Color.text (Color.make `Gray ~variant:`V700 ());
+
]); At.class' "mb-6"] [El.txt "Card Pattern"];
+
+
El.div ~at:[At.class' "grid grid-cols-1 md:grid-cols-3 gap-6"] [
+
El.div ~at:[classes_attr (tw [Patterns.card]); At.class' "p-6"] [
+
El.h3 ~at:[classes_attr (tw [
+
Typography.(to_class (font_size `Lg));
+
Typography.(to_class (font_weight `Semibold));
+
Color.text (Color.make `Gray ~variant:`V800 ());
+
]); At.class' "mb-2"] [El.txt "Card One"];
+
El.p ~at:[classes_attr (tw [
+
Color.text (Color.make `Gray ~variant:`V600 ());
+
])] [El.txt "This is a card using the built-in card pattern."];
+
];
+
+
El.div ~at:[classes_attr (tw [Patterns.card]); At.class' "p-6"] [
+
El.h3 ~at:[classes_attr (tw [
+
Typography.(to_class (font_size `Lg));
+
Typography.(to_class (font_weight `Semibold));
+
Color.text (Color.make `Gray ~variant:`V800 ());
+
]); At.class' "mb-2"] [El.txt "Card Two"];
+
El.p ~at:[classes_attr (tw [
+
Color.text (Color.make `Gray ~variant:`V600 ());
+
])] [El.txt "Another card with the same styling pattern applied."];
+
];
+
+
El.div ~at:[classes_attr (tw [Patterns.card]); At.class' "p-6"] [
+
El.h3 ~at:[classes_attr (tw [
+
Typography.(to_class (font_size `Lg));
+
Typography.(to_class (font_weight `Semibold));
+
Color.text (Color.make `Gray ~variant:`V800 ());
+
]); At.class' "mb-2"] [El.txt "Card Three"];
+
El.p ~at:[classes_attr (tw [
+
Color.text (Color.make `Gray ~variant:`V600 ());
+
])] [El.txt "A third card demonstrating consistent styling."];
+
];
+
];
+
];
+
+
(* Flex Center Pattern *)
+
El.section ~at:[At.class' "mb-12"] [
+
El.h2 ~at:[classes_attr (tw [
+
Typography.(to_class (font_size `Xl));
+
Typography.(to_class (font_weight `Semibold));
+
Color.text (Color.make `Gray ~variant:`V700 ());
+
]); At.class' "mb-6"] [El.txt "Flex Center Pattern"];
+
+
El.div ~at:[classes_attr (tw [Patterns.flex_center]); At.class' "bg-blue-50 rounded-lg h-32"] [
+
El.p ~at:[classes_attr (tw [
+
Color.text (Color.make `Blue ~variant:`V600 ());
+
Typography.(to_class (font_weight `Medium));
+
])] [El.txt "This content is perfectly centered using flex_center pattern"];
+
];
+
];
+
+
(* Stack Pattern *)
+
El.section ~at:[At.class' "mb-12"] [
+
El.h2 ~at:[classes_attr (tw [
+
Typography.(to_class (font_size `Xl));
+
Typography.(to_class (font_weight `Semibold));
+
Color.text (Color.make `Gray ~variant:`V700 ());
+
]); At.class' "mb-6"] [El.txt "Stack Pattern"];
+
+
El.div ~at:[classes_attr (tw [Patterns.stack ~gap:(Size.rem 1.0) ()]); At.class' "bg-white rounded-lg shadow-sm p-6"] [
+
El.div ~at:[classes_attr (tw [
+
Color.bg (Color.make `Green ~variant:`V50 ());
+
Spacing.(to_class (p (Size.rem 1.0)));
+
Effects.rounded_md;
+
])] [El.txt "Stack Item 1"];
+
+
El.div ~at:[classes_attr (tw [
+
Color.bg (Color.make `Blue ~variant:`V50 ());
+
Spacing.(to_class (p (Size.rem 1.0)));
+
Effects.rounded_md;
+
])] [El.txt "Stack Item 2"];
+
+
El.div ~at:[classes_attr (tw [
+
Color.bg (Color.make `Purple ~variant:`V50 ());
+
Spacing.(to_class (p (Size.rem 1.0)));
+
Effects.rounded_md;
+
])] [El.txt "Stack Item 3"];
+
];
+
];
+
+
(* Inline Stack Pattern *)
+
El.section [
+
El.h2 ~at:[classes_attr (tw [
+
Typography.(to_class (font_size `Xl));
+
Typography.(to_class (font_weight `Semibold));
+
Color.text (Color.make `Gray ~variant:`V700 ());
+
]); At.class' "mb-6"] [El.txt "Inline Stack Pattern"];
+
+
El.div ~at:[classes_attr (tw [Patterns.inline_stack ~gap:(Size.rem 1.0) ()]); At.class' "bg-white rounded-lg shadow-sm p-6"] [
+
El.span ~at:[classes_attr (tw [
+
Color.bg (Color.make `Red ~variant:`V50 ());
+
Color.text (Color.make `Red ~variant:`V600 ());
+
Spacing.(to_class (px (Size.rem 0.75)));
+
Spacing.(to_class (py (Size.rem 0.5)));
+
Effects.rounded_full;
+
Typography.(to_class (font_size `Sm));
+
])] [El.txt "Tag 1"];
+
+
El.span ~at:[classes_attr (tw [
+
Color.bg (Color.make `Yellow ~variant:`V50 ());
+
Color.text (Color.make `Yellow ~variant:`V600 ());
+
Spacing.(to_class (px (Size.rem 0.75)));
+
Spacing.(to_class (py (Size.rem 0.5)));
+
Effects.rounded_full;
+
Typography.(to_class (font_size `Sm));
+
])] [El.txt "Tag 2"];
+
+
El.span ~at:[classes_attr (tw [
+
Color.bg (Color.make `Indigo ~variant:`V50 ());
+
Color.text (Color.make `Indigo ~variant:`V600 ());
+
Spacing.(to_class (px (Size.rem 0.75)));
+
Spacing.(to_class (py (Size.rem 0.5)));
+
Effects.rounded_full;
+
Typography.(to_class (font_size `Sm));
+
])] [El.txt "Tag 3"];
+
];
+
];
+
];
+
];
+
] in
+
html_doc
+
+
let () =
+
(* Output HTML to stdout *)
+
let html_doc = create_patterns_demo () in
+
let html_string = El.to_string ~doctype:true html_doc in
+
print_string html_string
+80
lib/tailwind-html/cli.ml
···
+
(** Tailwind CLI integration for CSS processing *)
+
+
open Unix
+
+
type config = {
+
input_css: string; (** Path to input CSS file with Tailwind directives *)
+
output_css: string; (** Path to output CSS file *)
+
content: string list; (** List of content paths to scan for classes *)
+
minify: bool; (** Whether to minify the output *)
+
}
+
+
let default_config = {
+
input_css = "input.css";
+
output_css = "output.css";
+
content = ["*.html"; "*.ml"];
+
minify = true;
+
}
+
+
(** Check if Tailwind CLI is available *)
+
let check_tailwind_cli () =
+
try
+
let ic = open_process_in "npx @tailwindcss/cli --help 2>/dev/null" in
+
let _ = input_line ic in
+
let status = close_process_in ic in
+
status = WEXITED 0
+
with _ -> false
+
+
(** Generate Tailwind CSS from input file *)
+
let process_css config =
+
if not (check_tailwind_cli ()) then
+
failwith "Tailwind CLI not found. Install with: npm install -D @tailwindcss/cli"
+
else
+
let content_args = String.concat " " (List.map (fun p -> Printf.sprintf "--content '%s'" p) config.content) in
+
let minify_flag = if config.minify then "--minify" else "" in
+
let cmd = Printf.sprintf "npx @tailwindcss/cli -i %s -o %s %s %s 2>&1"
+
config.input_css
+
config.output_css
+
content_args
+
minify_flag
+
in
+
let ic = open_process_in cmd in
+
let rec read_output acc =
+
try
+
let line = input_line ic in
+
read_output (line :: acc)
+
with End_of_file ->
+
List.rev acc
+
in
+
let output = read_output [] in
+
let status = close_process_in ic in
+
match status with
+
| WEXITED 0 -> Ok output
+
| WEXITED n -> Error (Printf.sprintf "Tailwind CLI exited with code %d:\n%s" n (String.concat "\n" output))
+
| _ -> Error "Tailwind CLI terminated abnormally"
+
+
(** Process CSS for a specific HTML file *)
+
let process_for_html ~input_css ~html_file ~output_css ?(minify=true) () =
+
let config = {
+
input_css;
+
output_css;
+
content = [html_file];
+
minify;
+
} in
+
process_css config
+
+
(** Write HTML to file and process CSS *)
+
let write_and_process ~html_content ~html_file ~input_css ~output_css () =
+
(* Write HTML to file *)
+
let oc = open_out html_file in
+
output_string oc html_content;
+
close_out oc;
+
+
(* Process CSS *)
+
match process_for_html ~input_css ~html_file ~output_css () with
+
| Ok _ ->
+
Printf.eprintf "✅ Generated %s and %s\n" html_file output_css;
+
Ok ()
+
| Error msg ->
+
Printf.eprintf "❌ CSS processing failed: %s\n" msg;
+
Error msg
+34
lib/tailwind-html/cli.mli
···
+
(** Tailwind CLI integration for CSS processing *)
+
+
type config = {
+
input_css: string; (** Path to input CSS file with Tailwind directives *)
+
output_css: string; (** Path to output CSS file *)
+
content: string list; (** List of content paths to scan for classes *)
+
minify: bool; (** Whether to minify the output *)
+
}
+
+
val default_config : config
+
+
(** Check if Tailwind CLI is available *)
+
val check_tailwind_cli : unit -> bool
+
+
(** Generate Tailwind CSS from input file *)
+
val process_css : config -> (string list, string) result
+
+
(** Process CSS for a specific HTML file *)
+
val process_for_html :
+
input_css:string ->
+
html_file:string ->
+
output_css:string ->
+
?minify:bool ->
+
unit ->
+
(string list, string) result
+
+
(** Write HTML to file and process CSS *)
+
val write_and_process :
+
html_content:string ->
+
html_file:string ->
+
input_css:string ->
+
output_css:string ->
+
unit ->
+
(unit, string) result