Skip to content

How the Project is Organized

Modular has an opinionated directory structure suggested to organize your application. When you initiate a new Modular Project, you will have the default Laravel directories and files, along with a few additional ones described below.

Primarily, you will have two main directories:

  • The modules directory, where you will structure your PHP/Laravel related code, organized into modules, with app Routes, Controllers, Models, Validations, etc.

  • The resources directory, where you will structure your Vue.js related code, organized into Pages (instead of Blade views), Vue Components, CSS, etc.

All communication between your frontend (Vue 3) and backend (Laravel) will be handled using the Inertia.js adapter. Modular will provide some facilities to make this communication even smoother, exposing validation error messages, and other useful information to your Vue context.

The Modules Directory

You can regard the modules directory as domains of your application, and you are free to organize them as you see fit. The example below illustrates a simple web application with a Customer Management Module, and a Calendar Module, where it's possible to schedule events for existing customers.

The Modules Directory in this case, will be:

bash
./modules
├── Calendar
│   ├── CalendarServiceProvider.php
│   ├── Http
│   │   ├── Controllers
│   │   └── Requests
│   ├── Models
│   │   └── Calendar.php
│   └── routes
│       └── app.php
└── Customer
    ├── CustomerServiceProvider.php
    ├── Http
    │   ├── Controllers
    │   └── Requests
    ├── Models
    │   └── Customer.php
    └── routes
        └── app.php

Please note that both modules don't have a views directory. This is because the views are represented as Vue.js Page Components, and are located in the resources/js/Pages directory.

The Resources Directory

The resources directory holds significant importance in a Modular Application and follows the structure outlined below:

bash
./resources
├── css
│   └── app.css
├── images
│   └── logo.svg
├── js
│   ├── Components
│   ├── Composables
│   ├── Configs
│   ├── Layouts
│   ├── Pages
│   ├── Plugins
│   ├── Resolvers
│   ├── app.js
│   └── bootstrap.js
└── views
    ├── app.blade.php
    ├── components
    └── welcome.blade.php

The resources/css directory

In this directory, you'll find the app.css file, which serves as the main (entrypoint) CSS file for your application. The app.css file imports:

  • Tailwind CSS files, enabling you to utilize Tailwind CSS classes in your application.

Additionally, it defines several root CSS variables that can be utilized in your custom Vue Components and custom css files.

TIP

The color palette in the root CSS variables is derived from the Radix UI Scales. Each level (e.g., --color-primary-1, --color-primary-2, etc.) is intended for a specific context, such as:

  • App background
  • UI element background
  • UI element border and focus rings
  • Low-contrast text, etc.

The Radix Project is an excellent resource for learning about color palettes, composing a palette, understanding the scale, and how to apply them. This concept is embraced across all Custom Vue Components within the Modular project.

For instance, the root CSS variables adopt the following syntax:

css
@import "tailwindcss/base";
@import "tailwindcss/components";
@import "tailwindcss/utilities";

@layer base {
  :root {
    --color-primary-1: 253 253 254; /* #fdfdfe */
    --color-primary-2: 248 250 255; /* #f8faff */
    --color-primary-3: 240 244 255; /* #f0f4ff */
    --color-primary-4: 230 237 254; /* #e6edfe */
    --color-primary-5: 217 226 252; /* #d9e2fc */
    --color-primary-6: 198 212 249; /* #c6d4f9 */
    --color-primary-7: 174 192 245; /* #aec0f5 */
    --color-primary-8: 141 164 239; /* #8da4ef */
    --color-primary-9: 62 99 221; /* #3e63dd */
    --color-primary-10: 58 92 204; /* #3a5ccc */
    --color-primary-11: 52 81 178; /* #3451b2 */
    --color-primary-12: 31 45 92; /* #1f2d5c */

    /* 1 - App background, 2 - Subtle background */
    --color-neutral-1: 252 252 253; /* #fcfcfd */
    --color-neutral-2: 249 249 251; /* #f9f9fb */

    /* 3 - UI element background, 4 - Hovered UI element background, 5 - Active / Selected UI element background */
    --color-neutral-3: 242 242 245; /* #f2f2f5 */
    --color-neutral-4: 235 235 239; /* #ebebef */
    --color-neutral-5: 228 228 233; /* #e4e4e9 */

    /* 6 - Subtle borders and separators, 7 - UI element border and focus rings, 8 - Hovered UI element border */
    --color-neutral-6: 221 221 227; /* #dddde3 */
    --color-neutral-7: 211 212 219; /* #d3d4db */
    --color-neutral-8: 185 187 198; /* #b9bbc6 */

    /* 9 - Solid backgrounds, 10 - Hovered solid backgrounds */
    --color-neutral-9: 139 141 152; /* #8b8d98 */
    --color-neutral-10: 126 128 138; /* #7e808a */

    /* 11 - Low-contrast text, 12 - High-contrast text */
    --color-neutral-11: 96 100 108; /* #60646c */
    --color-neutral-12: 28 32 36; /* #1c2024 */

    /* Semantic Colors */
    --color-info: 11 104 203; /* #0b68cb 11*/
    --color-info-light: 237 246 255; /* #edf6ff 3*/
    --color-info-dark: 17 50 100; /* #113264 12*/

    --color-success: 24 121 78; /* #18794e */
    --color-success-light: 233 249 238; /* #e9f9ee */
    --color-success-dark: 25 59 45; /* #193b2d */

    --color-warning: 119 95 40; /* #775f28 */
    --color-warning-light: 255 248 198; /* #fff8c6 */
    --color-warning-dark: 71 59 31; /* #473b1f */

    --color-error: 198 42 47; /* #c62a2f */
    --color-error-light: 255 239 239; /* #ffefef */
    --color-error-dark: 100 23 35; /* #641723 */
  }

  .dark-theme {
    --color-primary-1: 253 253 254; /* #fdfdfe */
    --color-primary-2: 248 250 255; /* #f8faff */
    --color-primary-3: 240 244 255; /* #f0f4ff */
    --color-primary-4: 230 237 254; /* #e6edfe */
    --color-primary-5: 217 226 252; /* #d9e2fc */
    --color-primary-6: 198 212 249; /* #c6d4f9 */
    --color-primary-7: 174 192 245; /* #aec0f5 */
    --color-primary-8: 141 164 239; /* #8da4ef */
    --color-primary-9: 62 99 221; /* #3e63dd */
    --color-primary-10: 58 92 204; /* #3a5ccc */
    --color-primary-11: 52 81 178; /* #3451b2 */
    --color-primary-12: 31 45 92; /* #1f2d5c */

    /* 1 - App background, 2 - Subtle background */
    --color-neutral-1: 17 17 19; /* #111113 */
    --color-neutral-2: 24 25 27; /* #18191b */

    /* 3 - UI element background, 4 - Hovered UI element background, 5 - Active / Selected UI element background */
    --color-neutral-3: 33 34 37; /* #212225 */
    --color-neutral-4: 39 42 45; /* #272a2d */
    --color-neutral-5: 46 49 53; /* #2e3135 */

    /* 6 - Subtle borders and separators, 7 - UI element border and focus rings, 8 - Hovered UI element border */
    --color-neutral-6: 54 58 63; /* #363a3f */
    --color-neutral-7: 67 72 78; /* #43484e */
    --color-neutral-8: 90 97 105; /* #5a6169 */

    /* 9 - Solid backgrounds, 10 - Hovered solid backgrounds */
    --color-neutral-9: 105 110 119; /* #696e77 */
    --color-neutral-10: 119 123 132; /* #777b84 */

    /* 11 - Low-contrast text, 12 - High-contrast text */
    --color-neutral-11: 176 180 186; /* #b0b4ba */
    --color-neutral-12: 237 238 240; /* #edeef0 */

    /* Semantic Colors */
    --color-info: 11 104 203; /* #0b68cb 11*/
    --color-info-light: 237 246 255; /* #edf6ff 3*/
    --color-info-dark: 17 50 100; /* #113264 12*/

    --color-success: 24 121 78; /* #18794e */
    --color-success-light: 233 249 238; /* #e9f9ee */
    --color-success-dark: 25 59 45; /* #193b2d */

    --color-warning: 119 95 40; /* #775f28 */
    --color-warning-light: 255 248 198; /* #fff8c6 */
    --color-warning-dark: 71 59 31; /* #473b1f */

    --color-error: 198 42 47; /* #c62a2f */
    --color-error-light: 255 239 239; /* #ffefef */
    --color-error-dark: 100 23 35; /* #641723 */
  }

  html {
    @apply bg-skin-neutral-1 text-skin-neutral-12;
  }
}

@layer components {
  .input-error {
    @apply !ring-skin-error;
  }
}

And these variables can be used in your custom Vue Components, with the skin prefix, like this:

html
<h1 class="text-skin-primary-12">
  My Custom Title, with high-contrast text...
</h1>
<p class="text-skin-neutral-11">
  My Custom Description, with low-contrast text...
</p>

The variables will be available in your custom css files if/when needed, throught the Tailwind's theme function, like this:

scss
.button {
  color: theme(colors.skin.primary.11);
  background-color: theme(colors.skin.primary.3);
}

The resources/image directory

Here you will find the logo.svg file, which is used as the logo on the Authentication page and in the Sidebar of the logged view of your application. You can replace it with your own logo.

The resources/js directory

Here you will find the app.js file, which is the main (entrypoint) JavaScript file of your application. This app.js file will:

  • Import the app.css file, enabling it to be handled by Vite.
  • Import the Remix Icons CSS, allowing the library to be utilized in your project.
  • Configure support for streamlined translations in your Vue Components.
  • Import the Ziggy library, enabling the use of Laravel routes in your Vue Components.
  • Configure the Inertia app to utilize the resources mentioned above.

The resources/js/Components directory

Here you will find some Modular Components that are automatically available globally for use in your application. You are free to create your own global components here, and prefixing the component's name with App (like AppMyCustomComponent.vue), will ensure these components are automatically loaded in your application when needed, through the resources/js/Resolvers/AppComponentsResolver.js that configures the unplugin-vue-components to facilitate this autoload mechanism.

The resources/js/Composables directory

This directory houses some Vue Composables functions that assist in solving common tasks without code duplication, offering an easy-to-use API.

Some available Composables include:

  • useAuthCan: A Composable aiding with the ACL (Access Control List) System, allowing the logged user to view only the interface elements they have permissions to use.
  • useDataSearch: A Composable assisting in implementing a search feature in your data grids.
  • useFormContext: A Composable aiding in detecting the state of a form, whether it's in create or update mode.
  • useFormErrors: A Composable assisting in collecting, parsing, and displaying the validation errors reported by Laravel.
  • useTitle: A Composable aiding in setting the form title of the page, according to the form context, for example: Create User or Edit User.

The resources/js/Configs directory

This directory contains the menu.js file, which is the configuration file for the Sidebar menu of your application. You can add, remove, or edit the menu items here.

The resources/js/Layouts directory

This directory houses two layouts used by Modular:

  • The GuestLayout.vue layout, used by the Authentication page and reset password forms, primarily intended for unauthenticated users.
  • The AuthenticatedLayout.vue layout, used by the Dashboard page and all other pages of your application, primarily intended for authenticated users.

The resources/js/Pages directory

In this directory, you'll find the Pages returned by the Laravel Controllers as Inertia Pages. By default, each new module you create in the modules directory will have a corresponding directory within Pages. For instance, if you create a Customer module, a Customer directory will also be created inside the Pages directory, resembling:

bash
./modules
└── Customer

./resources/js/Pages/Customer
├── CustomerForm.vue
└── CustomerIndex.vue

The CustomerForm.vue file is designated for creating and editing customers, while the CustomerIndex.vue file is used for listing customers, typically in a searchable, paginated Data Grid.

As your pages begin to expand, it's advisable to break them down into smaller components, craft custom composables, configuration files, and so forth. Hence, it's usual to see a structure like the following within your Pages/Customer directory:

bash
./resources/js/Pages/Customer
├── Components
├── Composables
├── CustomerForm.vue
└── CustomerIndex.vue

This arrangement facilitates a more organized and manageable codebase, especially as the complexity of your pages increases.

You have the flexibility to organize your Pages directory as you see fit. A practical approach to begin with is to establish a 1:1 mapping between your module's controllers and your Pages directories. For instance, if you have two controllers named CustomerController and CustomerStatsController, you could create two corresponding directories within the Pages directory, like Pages/Customer and Pages/CustomerStats.

The resources/js/Plugins directory

This directory contains the Translations.js Modular Plugin, which globally provides a translate function __('name') to your Vue Components. It's integrated with the default Laravel translation files, so you can define the translations as usual in your Laravel applications, and utilize the same keys to translate content in your Vue Components.

The resources/js/Resolvers directory

This directory contains the AppComponentsResolver.js file. This file is the configuration file that sets up the unplugin-vue-components to autoload the Modular Global Components found in the resources/js/Components directory and prefixed with the App namespace.