{"id":14955347,"url":"https://github.com/ntamvl/rails7withreacttailwindcssbootstrapexample","last_synced_at":"2026-01-28T11:12:32.940Z","repository":{"id":52956910,"uuid":"440441478","full_name":"ntamvl/Rails7WithReactTailwindCSSBootstrapExample","owner":"ntamvl","description":"Rails 7 with React, TailwindCSS and Bootstrap 5 Example","archived":false,"fork":false,"pushed_at":"2022-08-04T15:49:58.000Z","size":4307,"stargazers_count":4,"open_issues_count":0,"forks_count":0,"subscribers_count":2,"default_branch":"main","last_synced_at":"2025-01-14T11:14:14.106Z","etag":null,"topics":["bootstrap5","rails","rails7","react","tailwindcss"],"latest_commit_sha":null,"homepage":"https://ntam.me/rails-7-react-tailwindcss-bootstrap-5-example/","language":"HTML","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/ntamvl.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2021-12-21T08:19:38.000Z","updated_at":"2024-02-21T10:47:55.000Z","dependencies_parsed_at":"2022-08-26T14:31:15.477Z","dependency_job_id":null,"html_url":"https://github.com/ntamvl/Rails7WithReactTailwindCSSBootstrapExample","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ntamvl%2FRails7WithReactTailwindCSSBootstrapExample","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ntamvl%2FRails7WithReactTailwindCSSBootstrapExample/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ntamvl%2FRails7WithReactTailwindCSSBootstrapExample/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ntamvl%2FRails7WithReactTailwindCSSBootstrapExample/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/ntamvl","download_url":"https://codeload.github.com/ntamvl/Rails7WithReactTailwindCSSBootstrapExample/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":234719887,"owners_count":18876527,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["bootstrap5","rails","rails7","react","tailwindcss"],"created_at":"2024-09-24T13:11:01.333Z","updated_at":"2025-10-01T18:31:08.945Z","avatar_url":"https://github.com/ntamvl.png","language":"HTML","readme":"# Rails 7 with React, TailwindCSS and Bootstrap 5 Example\n\nRails 7 with React, TailwindCSS and Bootstrap 5 Example\n- Ruby 3.0.3\n- Rails 7.0.0\n- SQLite\n- Node v14.15.0\n- NPM 6.14.8\n- Yarn 1.22.17\n- TailwindCSS 3\n- Bootstrap 5\n- React 17.0.2\n\n## Setup Rails 7 project\n**Create Rails 7 project**\n```bash\nrails _7.0.0_ new Rails7WithReactTailwindCSSBootstrapExample -j esbuild -c tailwind\n```\n\n**Install node packages**\n```bash\nyarn add @tailwindcss/forms @tailwindcss/typography bootstrap @popperjs/core jquery postcss-flexbugs-fixes postcss-import postcss-nested postcss-preset-env react react-dom prop-types\n```\n\n**Create PostCSS config file `postcss.config.js`**\n```js\nmodule.exports = {\n  plugins: [\n    require(\"autoprefixer\"),\n    require(\"postcss-import\"),\n    require(\"tailwindcss\"),\n    require(\"postcss-nested\"),\n    require(\"postcss-flexbugs-fixes\"),\n    require(\"postcss-preset-env\")({\n      autoprefixer: {\n        flexbox: \"no-2009\",\n      },\n      stage: 3,\n    }),\n  ],\n};\n\n```\n\n**Create esbuild plugin to load css file `esbuild.style.loader.plugin.js`**\n```js\n// esbuild.style.loader.plugin.js\n\nconst fs = require('fs');\n\nconst styleLoaderPlugin = {\n  name: 'styleLoader',\n  setup: build =\u003e {\n    // replace CSS imports with synthetic 'loadStyle' imports\n    build.onLoad({ filter: /\\.css$/ }, async args =\u003e {\n      return {\n        contents: `\n          import {loadStyle} from 'loadStyle';\n          loadStyle(${JSON.stringify(args.path)});\n        `,\n        loader: 'js',\n      };\n    });\n\n    // resolve 'loadStyle' imports to the virtual loadStyleShim namespace which is this plugin\n    build.onResolve({ filter: /^loadStyle$/ }, args =\u003e {\n      return { path: `loadStyle(${JSON.stringify(args.importer)})`, namespace: 'loadStyleShim' };\n    });\n\n    // define the loadStyle() function that injects CSS as a style tag\n    build.onLoad({ filter: /^loadStyle\\(.*\\)$/, namespace: 'loadStyleShim' }, async args =\u003e {\n      const match = /^loadStyle\\(\\\"(.*)\"\\)$/.exec(args.path);\n      const cssFilePath = match[1];\n      const cssFileContents = String(fs.readFileSync(cssFilePath));\n      return {\n        contents: `\n          export function loadStyle() {\n              const style = document.createElement('style');\n              style.innerText = \\`${cssFileContents}\\`;\n              document.querySelector('head').appendChild(style);\n          }\n        `,\n      };\n    });\n  },\n};\n\nmodule.exports = {\n  styleLoaderPlugin\n};\n\n```\n\n**Create file `esbuild.config.js`**\n```js\n// esbuild.config.js\n\nconst path = require('path')\nconst { styleLoaderPlugin } = require(\"./esbuild.style.loader.plugin\");\n\nrequire(\"esbuild\").build({\n  entryPoints: [\n    'application.js',\n    'react/hello_react.js',\n    'styles/index.css'\n  ],\n  bundle: true,\n  logLevel: 'info',\n  outdir: path.join(process.cwd(), \"app/assets/builds\"),\n  absWorkingDir: path.join(process.cwd(), \"app/javascript\"),\n  watch: process.argv.includes(\"--watch\"),\n  publicPath: '/assets',\n  loader: {\n    '.js': 'jsx',\n    '.png': 'file'\n  },\n  plugins: [\n    styleLoaderPlugin\n  ],\n}).catch(() =\u003e process.exit(1))\n\n```\n\n**Add build script to `package.json`**\n```json\n// ...\n\"scripts\": {\n    \"build\": \"node esbuild.config.js\",\n    \"build:css\": \"tailwindcss --postcss -i ./app/assets/stylesheets/application.tailwind.css -o ./app/assets/builds/application.css\"\n},\n// ...\n```\n\n**Generate tailwindcss config**\n```\nrm tailwind.config.js\nnpx tailwindcss init --full\n```\n\n**Update Tailwind config `tailwind.config.js`**\n```js\n// ...\ncontent: [\n    './app/views/**/*.html.erb',\n    './app/helpers/**/*.rb',\n    './app/javascript/**/*.js'\n],\nprefix: 'tw-',\n// ...\n```\n\n**Create custom styles `app/assets/stylesheets/styles.css`**\n```css\n/* app/assets/stylesheets/styles.css */\n/* Custom Styles */\n\n.my-styles {\n  font-weight: 600;\n  color: green;\n}\n\n```\n\n**Update file `application.tailwind.css`**\n```css\n/* this line is used for the case if prioritizes Bootstrap first */\n@import \"bootstrap/dist/css/bootstrap.css\";\n\n@import \"tailwindcss/base\";\n@import \"tailwindcss/components\";\n@import \"tailwindcss/utilities\";\n\n/* this line is used for the case if prioritizes Bootstrap later */\n/* @import \"bootstrap/dist/css/bootstrap.css\"; */\n@import \"./styles\";\n\n```\n\nCopy images to `app/assets/images/` base on this project\n- app/assets/images/beams.jpeg\n- app/assets/images/grid.svg\n- app/assets/images/logo.svg\n\n## Generate home, hello_react pages\n```\n./bin/rails g controller pages home hello_react\n\n```\n\n## Update `route.rb`\n```rb\nRails.application.routes.draw do\n  root 'pages#home'\n  get '/hello_react' =\u003e 'pages#hello_react'\nend\n```\n\n## Setup Bootstrap and jQuery\n```\nmkdir app/javascript/libs\ntouch app/javascript/libs/bootstrap.js\ntouch app/javascript/libs/jquery.js\ntouch app/javascript/libs/index.js\n```\n\n**Create bootstrap config `app/javascript/libs/bootstrap.js`**\n```js\n// app/javascript/libs/bootstrap.js\n\n// import \"bootstrap/dist/css/bootstrap.css\"\n// import \"bootstrap/dist/js/bootstrap.bundle.js\"\n\nconst bootstrap = require(\"bootstrap/dist/js/bootstrap.bundle.js\")\nconst popoverElements = document.querySelector('[data-bs-toggle=\"popover\"]')\nif (popoverElements) new bootstrap.Popover(popoverElements, { trigger: 'hover' })\n\n```\n\n**Create jQuery config `app/javascript/libs/jquery.js`**\n```js\n// app/javascript/libs/jquery.js\nimport jquery from 'jquery';\nwindow.jQuery = jquery;\nwindow.$ = jquery;\n\n```\n\nCreate file index.js to include bootstrap and jquery `app/javascript/libs/index.js`\n```js\n// app/javascript/libs/index.js\nimport \"./jquery\";\nimport \"./bootstrap\";\n```\n\nUpdate `app/javascript/application.js`\n```js\n// Entry point for the build script in your package.json\nimport \"@hotwired/turbo-rails\"\nimport \"./controllers\"\nimport \"./libs\"\n\n```\n\n## Create Tailwind components\n\nI use tailwind prefix `tw-` for this example. I have created a simple tool to generate tailwind prefix, link: http://tailwind-prefix-generator.ntam.me/\n\n----\napp/views/pages/_content_home_tw.html.erb\n```html\n\u003c!-- app/views/pages/_content_home_tw.html.erb --\u003e\n\u003cdiv class=\"tw-min-h-screen tw-bg-gray-50 tw-py-6 tw-flex tw-flex-col tw-justify-center tw-relative tw-overflow-hidden sm:tw-py-12\"\u003e\n  \u003cimg src=\"\u003c%= image_url('beams.jpg') %\u003e\" alt=\"\" class=\"tw-absolute tw-top-1/2 tw-left-1/2 tw--translate-x-1/2 tw--translate-y-1/2 tw-max-w-none\" width=\"1308\" /\u003e\n  \u003cdiv class=\"tw-absolute tw-inset-0 tw-bg-[url(\u003c%= image_url('grid.svg') %\u003e)] tw-bg-center [mask-image:tw-linear-gradient(180deg,white,rgba(255,255,255,0))]\"\u003e\u003c/div\u003e\n  \u003cdiv class=\"tw-relative tw-px-6 tw-pt-10 tw-pb-8 tw-bg-white tw-shadow-xl tw-ring-1 tw-ring-gray-900/5 sm:tw-max-w-lg sm:tw-mx-auto sm:tw-rounded-lg sm:tw-px-10\"\u003e\n    \u003cdiv class=\"tw-max-w-md tw-mx-auto\"\u003e\n      \u003cimg src=\"\u003c%= image_url('logo.svg') %\u003e\" class=\"tw-h-6\" /\u003e\n      \u003cdiv class=\"tw-divide-y tw-divide-gray-300/50\"\u003e\n        \u003cdiv class=\"tw-py-8 tw-text-base tw-leading-7 tw-space-y-6 tw-text-gray-600\"\u003e\n          \u003cp\u003eAn advanced online playground for Tailwind CSS, including support for things like:\u003c/p\u003e\n          \u003cul class=\"tw-space-y-4\"\u003e\n            \u003cli class=\"tw-flex tw-items-center\"\u003e\n              \u003csvg class=\"tw-w-6 tw-h-6 tw-flex-none tw-fill-sky-100 tw-stroke-sky-500 tw-stroke-2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"\u003e\n                \u003ccircle cx=\"12\" cy=\"12\" r=\"11\" /\u003e\n                \u003cpath d=\"m8 13 2.165 2.165a1 1 0 0 0 1.521-.126L16 9\" fill=\"none\" /\u003e\n              \u003c/svg\u003e\n              \u003cp class=\"tw-ml-4\"\u003e\n                Customizing your\n                \u003ccode class=\"tw-text-sm tw-font-bold tw-text-gray-900\"\u003etailwind.config.js\u003c/code\u003e file\n              \u003c/p\u003e\n            \u003c/li\u003e\n            \u003cli class=\"tw-flex tw-items-center\"\u003e\n              \u003csvg class=\"tw-w-6 tw-h-6 tw-flex-none tw-fill-sky-100 tw-stroke-sky-500 tw-stroke-2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"\u003e\n                \u003ccircle cx=\"12\" cy=\"12\" r=\"11\" /\u003e\n                \u003cpath d=\"m8 13 2.165 2.165a1 1 0 0 0 1.521-.126L16 9\" fill=\"none\" /\u003e\n              \u003c/svg\u003e\n              \u003cp class=\"tw-ml-4\"\u003e\n                Extracting classes with\n                \u003ccode class=\"tw-text-sm tw-font-bold tw-text-gray-900\"\u003e@apply\u003c/code\u003e\n              \u003c/p\u003e\n            \u003c/li\u003e\n            \u003cli class=\"tw-flex tw-items-center\"\u003e\n              \u003csvg class=\"tw-w-6 tw-h-6 tw-flex-none tw-fill-sky-100 tw-stroke-sky-500 tw-stroke-2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"\u003e\n                \u003ccircle cx=\"12\" cy=\"12\" r=\"11\" /\u003e\n                \u003cpath d=\"m8 13 2.165 2.165a1 1 0 0 0 1.521-.126L16 9\" fill=\"none\" /\u003e\n              \u003c/svg\u003e\n              \u003cp class=\"tw-ml-4\"\u003eCode completion with instant preview\u003c/p\u003e\n            \u003c/li\u003e\n          \u003c/ul\u003e\n          \u003cp\u003ePerfect for learning how the framework works, prototyping a new idea, or creating a demo to share online.\u003c/p\u003e\n        \u003c/div\u003e\n        \u003cdiv class=\"tw-pt-8 tw-text-base tw-leading-7 tw-font-semibold\"\u003e\n          \u003cp class=\"tw-text-gray-900\"\u003eWant to dig deeper into Tailwind?\u003c/p\u003e\n          \u003cp\u003e\n            \u003ca href=\"https://tailwindcss.com/docs\" class=\"tw-text-sky-500 hover:tw-text-sky-600\"\u003eRead the docs \u0026rarr;\u003c/a\u003e\n          \u003c/p\u003e\n        \u003c/div\u003e\n        \u003cdiv class=\"tw-pt-8\"\u003e\n          \u003cp class=\"my-styles\"\u003eMy Styles\u003c/p\u003e\n        \u003c/div\u003e\n      \u003c/div\u003e\n    \u003c/div\u003e\n  \u003c/div\u003e\n\u003c/div\u003e\n\n```\n\napp/views/pages/_content_tailwind_1.html.erb\n```html\n\u003cdiv class=\"tw-mt-4 tw-mb-3\"\u003e\n  \u003cdiv style=\"background-position:10px 10px\" class=\"tw-not-prose tw-relative tw-bg-grid-gray-100 tw-bg-gray-50 tw-rounded-xl tw-overflow-hidden\"\u003e\n    \u003cdiv class=\"tw-absolute tw-inset-0 tw-bg-gradient-to-b tw-from-gray-50 tw-opacity-60\"\u003e\u003c/div\u003e\n    \u003cdiv class=\"tw-relative tw-rounded-xl tw-overflow-auto tw-p-8\"\u003e\n      \u003cdiv class=\"tw-flex tw-flex-col-- tw-sm:flex-row tw-justify-center tw-gap-8 tw-sm:gap-16\"\u003e\n        \u003cdiv class=\"tw-flex tw-flex-col tw-items-center tw-tw-shrink-0\"\u003e\n          \u003cp class=\"tw-font-medium tw-text-sm tw-text-gray-500 tw-font-mono tw-text-center tw-mb-3\"\u003eshadow-cyan-500/50\u003c/p\u003e\n          \u003cbutton class=\"tw-py-2 tw-px-3 tw-bg-cyan-500 tw-text-white tw-text-sm tw-font-semibold tw-rounded-md tw-shadow-lg tw-shadow-cyan-500/50 tw-focus:outline-none\"\u003eSubscribe\u003c/button\u003e\n        \u003c/div\u003e\n        \u003cdiv class=\"tw-flex tw-flex-col tw-items-center tw-tw-shrink-0\"\u003e\n          \u003cp class=\"tw-font-medium tw-text-sm tw-text-gray-500 tw-font-mono tw-text-center tw-mb-3\"\u003eshadow-blue-500/50\u003c/p\u003e\n          \u003cbutton class=\"tw-py-2 tw-px-3 tw-bg-blue-500 tw-text-white tw-text-sm tw-font-semibold tw-rounded-md tw-shadow-lg tw-shadow-blue-500/50 tw-focus:outline-none\"\u003eSubscribe\u003c/button\u003e\n        \u003c/div\u003e\n        \u003cdiv class=\"tw-flex tw-flex-col tw-items-center tw-tw-shrink-0\"\u003e\n          \u003cp class=\"tw-font-medium tw-text-sm tw-text-gray-500 tw-font-mono tw-text-center tw-mb-3\"\u003eshadow-indigo-500/50\u003c/p\u003e\n          \u003cbutton class=\"tw-py-2 tw-px-3 tw-bg-indigo-500 tw-text-white tw-text-sm tw-font-semibold tw-rounded-md tw-shadow-lg tw-shadow-indigo-500/50 tw-focus:outline-none\"\u003eSubscribe\u003c/button\u003e\n        \u003c/div\u003e\n      \u003c/div\u003e\n    \u003c/div\u003e\n    \u003cdiv class=\"tw-absolute inset-0 tw-pointer-events-none tw-border tw-border-black/5 tw-rounded-xl\"\u003e\u003c/div\u003e\n  \u003c/div\u003e\n\u003c/div\u003e\n\n```\n\napp/views/pages/_content_tailwind_2.html.erb\n```html\n\u003cdiv class=\"tw-relative tw-rounded-xl tw-overflow-auto\"\u003e\n  \u003c!-- Snap Point --\u003e\n  \u003cdiv class=\"tw-flex ml-[50%] tw-items-end tw-justify-start tw-pt-10 tw-mb-6\"\u003e\n    \u003cdiv class=\"tw-ml-2 tw-rounded tw-font-mono text-[0.625rem] tw-leading-6 tw-px-1.5 tw-ring-1 tw-ring-inset tw-bg-indigo-50 tw-text-indigo-600 tw-ring-indigo-600\"\u003esnap point\u003c/div\u003e\n    \u003cdiv class=\"tw-absolute tw-top-0 tw-bottom-0 tw-left-1/2 tw-border-l tw-border-indigo-500\"\u003e\u003c/div\u003e\n  \u003c/div\u003e\n  \u003c!-- Contents --\u003e\n  \u003cdiv class=\"tw-relative tw-w-full tw-flex tw-gap-6 tw-snap-x tw-overflow-x-auto tw-pb-14\"\u003e\n    \u003cdiv class=\"tw-snap-center tw-shrink-0\"\u003e\n      \u003cdiv class=\"tw-shrink-0 tw-w-4 sm:tw-w-48\"\u003e\u003c/div\u003e\n    \u003c/div\u003e\n    \u003cdiv class=\"tw-snap-center tw-shrink-0 first:tw-pl-8 last:tw-pr-8\"\u003e\n      \u003cimg class=\"tw-shrink-0 tw-w-80 tw-h-40 tw-rounded-lg tw-shadow-xl tw-bg-white\" src=\"https://images.unsplash.com/photo-1604999565976-8913ad2ddb7c?ixlib=rb-1.2.1\u0026amp;ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8\u0026amp;auto=format\u0026amp;fit=crop\u0026amp;w=320\u0026amp;h=160\u0026amp;q=80\"\u003e\n    \u003c/div\u003e\n    \u003cdiv class=\"tw-snap-center tw-shrink-0 first:tw-pl-8 last:tw-pr-8\"\u003e\n      \u003cimg class=\"tw-shrink-0 tw-w-80 tw-h-40 tw-rounded-lg tw-shadow-xl tw-bg-white\" src=\"https://images.unsplash.com/photo-1540206351-d6465b3ac5c1?ixlib=rb-1.2.1\u0026amp;ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8\u0026amp;auto=format\u0026amp;fit=crop\u0026amp;w=320\u0026amp;h=160\u0026amp;q=80\"\u003e\n    \u003c/div\u003e\n    \u003cdiv class=\"tw-snap-center tw-shrink-0 first:tw-pl-8 last:tw-pr-8\"\u003e\n      \u003cimg class=\"tw-shrink-0 tw-w-80 tw-h-40 tw-rounded-lg tw-shadow-xl tw-bg-white\" src=\"https://images.unsplash.com/photo-1622890806166-111d7f6c7c97?ixlib=rb-1.2.1\u0026amp;ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8\u0026amp;auto=format\u0026amp;fit=crop\u0026amp;w=320\u0026amp;h=160\u0026amp;q=80\"\u003e\n    \u003c/div\u003e\n    \u003cdiv class=\"tw-snap-center tw-shrink-0 first:tw-pl-8 last:tw-pr-8\"\u003e\n      \u003cimg class=\"tw-shrink-0 tw-w-80 tw-h-40 tw-rounded-lg tw-shadow-xl tw-bg-white\" src=\"https://images.unsplash.com/photo-1590523277543-a94d2e4eb00b?ixlib=rb-1.2.1\u0026amp;ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8\u0026amp;auto=format\u0026amp;fit=crop\u0026amp;w=320\u0026amp;h=160\u0026amp;q=80\"\u003e\n    \u003c/div\u003e\n    \u003cdiv class=\"tw-snap-center tw-shrink-0 first:tw-pl-8 last:tw-pr-8\"\u003e\n      \u003cimg class=\"tw-shrink-0 tw-w-80 tw-h-40 tw-rounded-lg tw-shadow-xl tw-bg-white\" src=\"https://images.unsplash.com/photo-1575424909138-46b05e5919ec?ixlib=rb-1.2.1\u0026amp;ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8\u0026amp;auto=format\u0026amp;fit=crop\u0026amp;w=320\u0026amp;h=160\u0026amp;q=80\"\u003e\n    \u003c/div\u003e\n    \u003cdiv class=\"tw-snap-center tw-shrink-0 first:tw-pl-8 last:tw-pr-8\"\u003e\n      \u003cimg class=\"tw-shrink-0 tw-w-80 tw-h-40 tw-rounded-lg tw-shadow-xl tw-bg-white\" src=\"https://images.unsplash.com/photo-1559333086-b0a56225a93c?ixlib=rb-1.2.1\u0026amp;ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8\u0026amp;auto=format\u0026amp;fit=crop\u0026amp;w=320\u0026amp;h=160\u0026amp;q=80\"\u003e\n    \u003c/div\u003e\n    \u003cdiv class=\"tw-snap-center tw-shrink-0\"\u003e\n      \u003cdiv class=\"tw-shrink-0 tw-w-4 sm:tw-w-48\"\u003e\u003c/div\u003e\n    \u003c/div\u003e\n  \u003c/div\u003e\n\u003c/div\u003e\n\n```\n\napp/views/pages/_content_tailwind_3.html.erb\n```html\n\u003cdiv class=\"tw-mt-4 tw--mb-3\"\u003e\n  \u003cdiv class=\"not-prose tw-mb-4 tw-flex tw-space-x-2\"\u003e\u003csvg class=\"tw-flex-none tw-w-5 tw-h-5 tw-text-gray-400\" viewBox=\"0 0 20 20\" fill=\"none\" aria-hidden=\"true\"\u003e\u003cpath d=\"m9.813 9.25.346-5.138a1.276 1.276 0 0 0-2.54-.235L6.75 11.25 5.147 9.327a1.605 1.605 0 0 0-2.388-.085.018.018 0 0 0-.004.019l1.98 4.87a5 5 0 0 0 4.631 3.119h3.885a4 4 0 0 0 4-4v-1a3 3 0 0 0-3-3H9.813ZM3 5s.35-.47 1.25-.828m9.516-.422c2.078.593 3.484 1.5 3.484 1.5\" stroke=\"currentColor\" stroke-width=\"1.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\"\u003e\u003c/path\u003e\u003c/svg\u003e\n    \u003cp class=\"tw-text-gray-700 tw-text-sm tw-font-medium\"\u003eScroll in the tw-grid of images to see the expected behaviour\u003c/p\u003e\n  \u003c/div\u003e\n  \u003cdiv class=\"not-prose tw-relative bg-grid-gray-100 tw-bg-gray-50 tw-rounded-xl tw-overflow-hidden\" style=\"background-position: 10px 10px;\"\u003e\n    \u003cdiv class=\"tw-absolute tw-inset-0 tw-bg-gradient-to-b tw-from-gray-50 tw-opacity-60\"\u003e\u003c/div\u003e\n    \u003cdiv class=\"tw-relative tw-rounded-xl tw-overflow-auto\"\u003e\n      \u003c!-- Snap Point --\u003e\n      \u003cdiv class=\"tw-flex ml-[50%] tw-items-end tw-justify-start tw-pt-10 tw-mb-6\"\u003e\n        \u003cdiv class=\"tw-ml-2 tw-rounded tw-font-mono text-[0.625rem] tw-leading-6 tw-px-1.5 tw-ring-1 tw-ring-inset tw-bg-indigo-50 tw-text-indigo-600 tw-ring-indigo-600\"\u003esnap point\u003c/div\u003e\n        \u003cdiv class=\"tw-absolute tw-top-0 tw-bottom-0 tw-left-1/2 tw-border-l tw-border-indigo-500\"\u003e\u003c/div\u003e\n      \u003c/div\u003e\n      \u003c!-- Contents --\u003e\n      \u003cdiv class=\"tw-relative tw-w-full tw-flex tw-gap-6 tw-snap-x tw-snap-mandatory tw-overflow-x-auto tw-pb-14\"\u003e\n        \u003cdiv class=\"tw-snap-center tw-shrink-0\"\u003e\n          \u003cdiv class=\"tw-shrink-0 tw-w-4 sm:tw-w-48\"\u003e\u003c/div\u003e\n        \u003c/div\u003e\n        \u003cdiv class=\"tw-snap-always tw-snap-center tw-shrink-0 first:tw-pl-8 last:tw-pr-8\"\u003e\n          \u003cimg class=\"tw-shrink-0 tw-w-80 tw-h-40 tw-object-cover tw-rounded-lg tw-shadow-xl tw-bg-white\" src=\"https://images.unsplash.com/photo-1604999565976-8913ad2ddb7c?ixlib=rb-1.2.1\u0026amp;ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8\u0026amp;auto=format\u0026amp;fit=crop\u0026amp;w=320\u0026amp;h=160\u0026amp;q=80\"\u003e\n        \u003c/div\u003e\n        \u003cdiv class=\"tw-snap-always tw-snap-center tw-shrink-0 first:tw-pl-8 last:tw-pr-8\"\u003e\n          \u003cimg class=\"tw-shrink-0 tw-w-80 tw-h-40 tw-object-cover tw-rounded-lg tw-shadow-xl tw-bg-white\" src=\"https://images.unsplash.com/photo-1540206351-d6465b3ac5c1?ixlib=rb-1.2.1\u0026amp;ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8\u0026amp;auto=format\u0026amp;fit=crop\u0026amp;w=320\u0026amp;h=160\u0026amp;q=80\"\u003e\n        \u003c/div\u003e\n        \u003cdiv class=\"tw-snap-always tw-snap-center tw-shrink-0 first:tw-pl-8 last:tw-pr-8\"\u003e\n          \u003cimg class=\"tw-shrink-0 tw-w-80 tw-h-40 tw-object-cover tw-rounded-lg tw-shadow-xl tw-bg-white\" src=\"https://images.unsplash.com/photo-1622890806166-111d7f6c7c97?ixlib=rb-1.2.1\u0026amp;ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8\u0026amp;auto=format\u0026amp;fit=crop\u0026amp;w=320\u0026amp;h=160\u0026amp;q=80\"\u003e\n        \u003c/div\u003e\n        \u003cdiv class=\"tw-snap-always tw-snap-center tw-shrink-0 first:tw-pl-8 last:tw-pr-8\"\u003e\n          \u003cimg class=\"tw-shrink-0 tw-w-80 tw-h-40 tw-object-cover tw-rounded-lg tw-shadow-xl tw-bg-white\" src=\"https://images.unsplash.com/photo-1590523277543-a94d2e4eb00b?ixlib=rb-1.2.1\u0026amp;ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8\u0026amp;auto=format\u0026amp;fit=crop\u0026amp;w=320\u0026amp;h=160\u0026amp;q=80\"\u003e\n        \u003c/div\u003e\n        \u003cdiv class=\"tw-snap-always tw-snap-center tw-shrink-0 first:tw-pl-8 last:tw-pr-8\"\u003e\n          \u003cimg class=\"tw-shrink-0 tw-w-80 tw-h-40 tw-object-cover tw-rounded-lg tw-shadow-xl tw-bg-white\" src=\"https://images.unsplash.com/photo-1575424909138-46b05e5919ec?ixlib=rb-1.2.1\u0026amp;ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8\u0026amp;auto=format\u0026amp;fit=crop\u0026amp;w=320\u0026amp;h=160\u0026amp;q=80\"\u003e\n        \u003c/div\u003e\n        \u003cdiv class=\"tw-snap-always tw-snap-center tw-shrink-0 first:tw-pl-8 last:tw-pr-8\"\u003e\n          \u003cimg class=\"tw-shrink-0 tw-w-80 tw-h-40 tw-object-cover tw-rounded-lg tw-shadow-xl tw-bg-white\" src=\"https://images.unsplash.com/photo-1559333086-b0a56225a93c?ixlib=rb-1.2.1\u0026amp;ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8\u0026amp;auto=format\u0026amp;fit=crop\u0026amp;w=320\u0026amp;h=160\u0026amp;q=80\"\u003e\n        \u003c/div\u003e\n        \u003cdiv class=\"tw-snap-center tw-shrink-0\"\u003e\n          \u003cdiv class=\"tw-shrink-0 tw-w-4 sm:tw-w-48\"\u003e\u003c/div\u003e\n        \u003c/div\u003e\n      \u003c/div\u003e\n    \u003c/div\u003e\n    \u003cdiv class=\"tw-absolute tw-inset-0 tw-pointer-events-none tw-border tw-border-black/5 tw-rounded-xl\"\u003e\u003c/div\u003e\n  \u003c/div\u003e\n\u003c/div\u003e\n\n```\n\n## Create Bootstrap components\napp/views/pages/_nav_bootstrap.html.erb\n```html\n\u003cdiv class=\"container-fluid\"\u003e\n  \u003cnav class=\"navbar navbar-expand-lg navbar-light bg-light\"\u003e\n    \u003cdiv class=\"container-fluid\"\u003e\n      \u003ca class=\"navbar-brand\" href=\"/\" target=\"_top\"\u003eBlogRails7\u003c/a\u003e\n      \u003cbutton class=\"navbar-toggler\" type=\"button\" data-bs-toggle=\"collapse\" data-bs-target=\"#navbarSupportedContent\" aria-controls=\"navbarSupportedContent\" aria-expanded=\"false\" aria-label=\"Toggle navigation\"\u003e\n        \u003cspan class=\"navbar-toggler-icon\"\u003e\u003c/span\u003e\n      \u003c/button\u003e\n      \u003cdiv class=\"collapse navbar-collapse\" id=\"navbarSupportedContent\"\u003e\n        \u003cul class=\"navbar-nav me-auto mb-2 mb-lg-0\"\u003e\n          \u003cli class=\"nav-item\"\u003e\n            \u003ca class=\"nav-link active\" aria-current=\"page\" href=\"/\" target=\"_top\"\u003eHome\u003c/a\u003e\n          \u003c/li\u003e\n          \u003cli class=\"nav-item\"\u003e\n            \u003ca class=\"nav-link\" href=\"/hello_react\" target=\"_top\"\u003eHello React\u003c/a\u003e\n          \u003c/li\u003e\n          \u003cli class=\"nav-item dropdown\"\u003e\n            \u003ca class=\"nav-link dropdown-toggle\" href=\"#\" id=\"navbarDropdown\" role=\"button\" data-bs-toggle=\"dropdown\" aria-expanded=\"false\"\u003e\n              Dropdown\n            \u003c/a\u003e\n            \u003cul class=\"dropdown-menu\" aria-labelledby=\"navbarDropdown\"\u003e\n              \u003cli\u003e\u003ca class=\"dropdown-item\" href=\"#\"\u003eAction\u003c/a\u003e\u003c/li\u003e\n              \u003cli\u003e\u003ca class=\"dropdown-item\" href=\"#\"\u003eAnother action\u003c/a\u003e\u003c/li\u003e\n              \u003cli\u003e\u003chr class=\"dropdown-divider\"\u003e\u003c/li\u003e\n              \u003cli\u003e\u003ca class=\"dropdown-item\" href=\"#\"\u003eSomething else here\u003c/a\u003e\u003c/li\u003e\n            \u003c/ul\u003e\n          \u003c/li\u003e\n          \u003cli class=\"nav-item\"\u003e\n            \u003ca class=\"nav-link disabled\"\u003eDisabled\u003c/a\u003e\n          \u003c/li\u003e\n        \u003c/ul\u003e\n        \u003cform class=\"d-flex\"\u003e\n          \u003cinput class=\"form-control me-2\" type=\"search\" placeholder=\"Search\" aria-label=\"Search\"\u003e\n          \u003cbutton class=\"btn btn-outline-success\" type=\"submit\"\u003eSearch\u003c/button\u003e\n        \u003c/form\u003e\n      \u003c/div\u003e\n    \u003c/div\u003e\n  \u003c/nav\u003e\n\u003c/div\u003e\n\n```\n\n## Create React component\napp/javascript/react/components/MyClock/styles.css\n```css\n.Clock {\n  padding: 5px;\n  margin-top: 15px;\n  margin-left: auto;\n  margin-right: auto;\n}\n```\n\napp/javascript/react/components/MyClock/MyClock.js\n```js\nimport React, { Component } from 'react'\nimport PropTypes from 'prop-types';\n\nexport class MyClock extends Component {\n  render() {\n    return (\n      \u003cdiv\u003e\n        \u003cdiv className=\"row\"\u003e\n          \u003cdiv className=\"col-lg-12 tw-flex tw-justify-center\"\u003e\n            \u003cClock size={400} timeFormat=\"24hour\" hourFormat=\"standard\" /\u003e\n          \u003c/div\u003e\n        \u003c/div\u003e\n      \u003c/div\u003e\n    );\n  }\n}\n\nexport default MyClock\n\nexport class Clock extends Component {\n  constructor(props) {\n    super(props);\n\n    this.state = { time: new Date() };\n    this.radius = this.props.size / 2;\n    this.drawingContext = null;\n    this.draw24hour = this.props.timeFormat.toLowerCase().trim() === \"24hour\";\n    this.drawRoman = !this.draw24hour \u0026\u0026 this.props.hourFormat.toLowerCase().trim() === \"roman\";\n\n  }\n\n  componentDidMount() {\n    this.getDrawingContext();\n    this.timerId = setInterval(() =\u003e this.tick(), 1000);\n  }\n\n  componentWillUnmount() {\n    clearInterval(this.timerId);\n  }\n\n  getDrawingContext() {\n    this.drawingContext = this.refs.clockCanvas.getContext('2d');\n    this.drawingContext.translate(this.radius, this.radius);\n    this.radius *= 0.9;\n  }\n\n  tick() {\n    this.setState({ time: new Date() });\n    const radius = this.radius;\n    let ctx = this.drawingContext;\n    this.drawFace(ctx, radius);\n    this.drawNumbers(ctx, radius);\n    this.drawTicks(ctx, radius);\n    this.drawTime(ctx, radius);\n  }\n\n  drawFace(ctx, radius) {\n    ctx.beginPath();\n    ctx.arc(0, 0, radius, 0, 2 * Math.PI);\n    ctx.fillStyle = \"white\";\n    ctx.fill();\n\n    const grad = ctx.createRadialGradient(0, 0, radius * 0.95, 0, 0, radius * 1.05);\n    grad.addColorStop(0, \"#333\");\n    grad.addColorStop(0.5, \"white\");\n    grad.addColorStop(1, \"#333\");\n    ctx.strokeStyle = grad;\n    ctx.lineWidth = radius * 0.1;\n    ctx.stroke();\n\n    ctx.beginPath();\n    ctx.arc(0, 0, radius * 0.05, 0, 2 * Math.PI);\n    ctx.fillStyle = \"#333\";\n    ctx.fill();\n  }\n\n  drawNumbers(ctx, radius) {\n    const romans = [\"I\", \"II\", \"III\", \"IV\", \"V\", \"VI\", \"VII\", \"VIII\", \"IX\", \"X\", \"XI\", \"XII\"];\n    const fontBig = radius * 0.15 + \"px Arial\";\n    const fontSmall = radius * 0.075 + \"px Arial\";\n    let ang, num;\n\n    ctx.textBaseline = \"middle\";\n    ctx.textAlign = \"center\";\n    for (num = 1; num \u003c 13; num++) {\n      ang = num * Math.PI / 6;\n      ctx.rotate(ang);\n      ctx.translate(0, -radius * 0.78);\n      ctx.rotate(-ang);\n      ctx.font = fontBig;\n      ctx.fillStyle = \"black\";\n      ctx.fillText(this.drawRoman ? romans[num - 1] : num.toString(), 0, 0);\n      ctx.rotate(ang);\n      ctx.translate(0, radius * 0.78);\n      ctx.rotate(-ang);\n\n      // Draw inner numerals for 24 hour time format\n      if (this.draw24hour) {\n        ctx.rotate(ang);\n        ctx.translate(0, -radius * 0.60);\n        ctx.rotate(-ang);\n        ctx.font = fontSmall;\n        ctx.fillStyle = \"red\";\n        ctx.fillText((num + 12).toString(), 0, 0);\n        ctx.rotate(ang);\n        ctx.translate(0, radius * 0.60);\n        ctx.rotate(-ang);\n      }\n    }\n\n    // Write author text\n    ctx.font = fontSmall;\n    ctx.fillStyle = \"#3D3B3D\";\n    ctx.translate(0, radius * 0.30);\n    ctx.fillText(\"React Clock\", 0, 0);\n    ctx.translate(0, -radius * 0.30);\n  }\n\n  drawTicks(ctx, radius) {\n    let numTicks, tickAng, tickX, tickY;\n\n    for (numTicks = 0; numTicks \u003c 60; numTicks++) {\n\n      tickAng = (numTicks * Math.PI / 30);\n      tickX = radius * Math.sin(tickAng);\n      tickY = -radius * Math.cos(tickAng);\n\n      ctx.beginPath();\n      ctx.lineWidth = radius * 0.010;\n      ctx.moveTo(tickX, tickY);\n      if (numTicks % 5 === 0) {\n        ctx.lineTo(tickX * 0.88, tickY * 0.88);\n      } else {\n        ctx.lineTo(tickX * 0.92, tickY * 0.92);\n      }\n      ctx.stroke();\n    }\n  }\n\n  drawTime(ctx, radius) {\n    const now = this.state.time;\n    let hour = now.getHours();\n    let minute = now.getMinutes();\n    let second = now.getSeconds();\n\n    // hour\n    hour %= 12;\n    hour = (hour * Math.PI / 6) + (minute * Math.PI / (6 * 60)) + (second * Math.PI / (360 * 60));\n    this.drawHand(ctx, hour, radius * 0.5, radius * 0.05);\n    // minute\n    minute = (minute * Math.PI / 30) + (second * Math.PI / (30 * 60));\n    this.drawHand(ctx, minute, radius * 0.8, radius * 0.05);\n    // second\n    second = (second * Math.PI / 30);\n    this.drawHand(ctx, second, radius * 0.9, radius * 0.02, \"red\");\n  }\n\n  drawHand(ctx, position, length, width, color) {\n    color = color || \"black\";\n    ctx.beginPath();\n    ctx.lineWidth = width;\n    ctx.lineCap = \"round\";\n    ctx.fillStyle = color;\n    ctx.strokeStyle = color;\n    ctx.moveTo(0, 0);\n    ctx.rotate(position);\n    ctx.lineTo(0, -length);\n    ctx.stroke();\n    ctx.rotate(-position);\n  }\n\n  render() {\n    return (\n      \u003cdiv className=\"Clock\" style={{ width: String(this.props.size) + 'px' }}\u003e\n        \u003ccanvas width={this.props.size} height={this.props.size} ref=\"clockCanvas\" /\u003e\n      \u003c/div\u003e\n    );\n  }\n}\n\nClock.defaultProps = {\n  size: 400, // size in pixels =\u003e size is length \u0026 width\n  timeFormat: \"24hour\", // {standard | 24hour} =\u003e if '24hour', hourFormat must be 'standard'\n  hourFormat: \"standard\" // {standard | roman}\n};\n\nClock.propTypes = {\n  size: PropTypes.number,\n  timeFormat: PropTypes.string,\n  hourFormat: PropTypes.string\n};\n\n```\n\napp/javascript/react/components/App/styles.css\n```css\n.my_styles_3 {\n  font-family: 600;\n  font-size: 2rem;\n  color: pink;\n  text-align: center;\n}\n```\n\napp/javascript/react/components/App/index.js\n```js\nimport React from 'react'\nimport MyClock from '../MyClock/MyClock'\nimport \"./styles.css\";\n\nexport const App = () =\u003e {\n  return (\n    \u003cdiv className=\"container tw-bg-gray-700 tw-rounded-xl tw-py-4\"\u003e\n      \u003cdiv className={'tw-py-4'}\u003e\n        \u003cdiv className={'my_styles_3'}\u003eHello React!!!\u003c/div\u003e\n      \u003c/div\u003e\n      \u003cMyClock /\u003e\n    \u003c/div\u003e\n  )\n}\n\nexport default App\n\n```\n\napp/javascript/react/hello_react.js\n```js\nimport React from \"react\";\nimport { render } from \"react-dom\";\nimport App from \"./components/App\";\n\ndocument.addEventListener(\"DOMContentLoaded\", () =\u003e {\n  render(\u003cApp /\u003e, document.getElementById('react-components'));\n});\n\n```\n\n\n## Render Tailwind and Bootstrap 5 components\nUpdate file `app/views/pages/home.html.erb`\n```html\n\u003c%= render 'pages/nav_bootstrap' %\u003e\n\u003c%= render 'pages/content_home_tw' %\u003e\n```\n\n## Render React components\nUpdate file `app/views/pages/hello_react.html.erb`\n```html\n\u003c%= render 'pages/nav_bootstrap' %\u003e\n\u003c%= render 'pages/popover_bs' %\u003e\n\n\u003cdiv class=\"container mt-4 tw-rounded-xl tw-bg-gray-700\"\u003e\n  \u003ch2 class=\"tw-text-3xl tw-text-white tw-py-4\"\u003eReact Components\u003c/h2\u003e\n\u003c/div\u003e\n\u003cdiv id=\"react-components\" class=\"tw-py-2\"\u003e\u003c/div\u003e\n\u003c%= javascript_include_tag \"react/hello_react\" %\u003e\n\n\u003cdiv class=\"container tw-mt-8\"\u003e\n  \u003ch2\u003eTailwind\u003c/h2\u003e\n  \u003c%= render 'pages/content_tailwind_1' %\u003e\n  \u003c%= render 'pages/content_tailwind_2' %\u003e\n  \u003c%= render 'pages/content_tailwind_3' %\u003e\n  \u003cdiv class=\"tw-py-8\"\u003e\u003c/div\u003e\n\u003c/div\u003e\n\n```\n\napp/javascript/styles/index.css\n```css\n.my-styles-2 {\n  font-weight: 600;\n  color: red;\n}\n```\n\n## If you want to use SCSS/SASS\nAdd package `esbuild-plugin-sass`\n```bash\nyarn add esbuild-plugin-sass\n```\n\nUpdate `esbuild.config.js`\n```js\n// ....\nconst sassPlugin = require('esbuild-plugin-sass')\n\n// ...\nplugins: [\n    styleLoaderPlugin,\n    sassPlugin()\n],\n\n// ...\n\n```\n\nAnd then you can create file like `styles.scss` and import it\nExample:\n\n```scss\n// styles2.scss\n.my_styles_4 {\n  font-family: 600;\n  font-size: 2rem;\n  color: red;\n  text-align: center;\n  cursor: pointer;\n  \u0026:hover {\n    color: green;\n  }\n}\n```\n\n```js\nimport React from 'react'\nimport MyClock from '../MyClock/MyClock'\nimport \"./styles2.scss\";\n\nexport const App = () =\u003e {\n  return (\n    \u003cdiv className=\"container tw-bg-gray-700 tw-rounded-xl tw-py-4\"\u003e\n      \u003cdiv className={'tw-py-4'}\u003e\n        \u003cdiv className={'my_styles_3'}\u003eHello React!!! 000\u003c/div\u003e\n        \u003cdiv className={'my_styles_4'}\u003eStyles SCSS\u003c/div\u003e\n      \u003c/div\u003e\n      \u003cMyClock /\u003e\n    \u003c/div\u003e\n  )\n}\n```\n\n## Create Article\n```bash\n./bin/rails g scaffold Article title:string body:text\n./bin/rails db:create db:migrate\n```\n\n*Use Tailwind and Bootstrap to update styles for article pages*\n\n## Run app\n```bash\n./bin/dev\n```\n\nThen go to http://localhost:3000/\n\nOR clone source and run to see the result:\n```bash\ncd\ngit clone https://github.com/ntamvl/Rails7WithReactTailwindCSSBootstrapExample.git\ncd Rails7WithReactTailwindCSSBootstrapExample\nbundle install\nyarn install\n./bin/dev\n```\n\nScreenshots:\n![Rails 7 with React, TailwindCSS and Bootstrap 5 Example](https://raw.githubusercontent.com/ntamvl/Rails7WithReactTailwindCSSBootstrapExample/main/screenshot_1.png)\n![Rails 7 with React, TailwindCSS and Bootstrap 5 Example](https://raw.githubusercontent.com/ntamvl/Rails7WithReactTailwindCSSBootstrapExample/main/screenshot_2.png)\n\nEnjoy ^_^ :))\n\n---\nReferences:\n- https://rubyonrails.org/2021/12/15/Rails-7-fulfilling-a-vision\n- https://getbootstrap.com/docs/5.1/getting-started/introduction/\n- https://tailwindcss.com/docs/installation\n","funding_links":[],"categories":[],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fntamvl%2Frails7withreacttailwindcssbootstrapexample","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fntamvl%2Frails7withreacttailwindcssbootstrapexample","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fntamvl%2Frails7withreacttailwindcssbootstrapexample/lists"}