Skip to content

Last updated:

Styling Overview

CPR uses a Tailwind-first approach to styling. Nearly all visual presentation is handled through Tailwind CSS utility classes applied directly in Vue templates, with a small set of reusable component classes defined in a global CSS entry point.

Core Principles

  1. Utility-first -- Prefer inline Tailwind classes over custom CSS. Most components contain zero <style> blocks.
  2. Component-based abstraction -- When a pattern repeats across many templates (buttons, inputs, cards), extract it into either a Vue component with computed classes or a @layer components class in main.css.
  3. Minimal custom CSS -- The only custom CSS lives in app/assets/css/main.css. There is no separate SCSS/LESS pipeline.
  4. Brand color system -- The primary brand color (#117ea7) is applied consistently through a mix of Tailwind's primary-* palette and arbitrary values like bg-[#117ea7].

Technology Stack

LayerTool
CSS frameworkTailwind CSS via @nuxtjs/tailwindcss
ConfigurationDefault Tailwind config (no tailwind.config.* file)
CSS entry pointapp/assets/css/main.css
Component stylingComputed class strings in Vue <script setup>
Icons@heroicons/vue (24px outline and solid variants)

How Styles Are Organized

Global styles -- main.css

The global stylesheet sets up three Tailwind layers and a small amount of non-layer CSS:

css
@tailwind base;       /* Tailwind's reset + base layer overrides */
@tailwind components; /* Reusable component classes (.btn-primary, .card, etc.) */
@tailwind utilities;  /* All utility classes */
  • @layer base -- Sets default body background and text color.
  • @layer components -- Defines shared component classes that are used across multiple templates (.btn-primary, .btn-secondary, .input-field, .card).
  • Non-layer CSS -- Custom scrollbar styling (.custom-scrollbar).

Component-level styles -- Computed classes

UI components like UiButton, UiInput, and UiModal build their class strings dynamically via computed properties or inline expressions. This keeps variant logic (size, color, disabled state) co-located with the component:

vue
<script setup lang="ts">
const buttonClasses = computed(() => [
  // base
  'font-medium rounded-lg transition-colors focus:outline-none focus:ring-2',
  // variant
  props.variant === 'primary'
    ? 'bg-[#117ea7] text-white hover:bg-[#0e6b8f]'
    : 'bg-gray-200 text-gray-800 hover:bg-gray-300',
  // size
  props.size === 'sm' ? 'px-3 py-1.5 text-sm' : 'px-4 py-2',
])
</script>

No scoped styles (in most cases)

The vast majority of CPR components have no <style> block. Tailwind utilities and computed classes cover all needs. If you find yourself reaching for a <style scoped> block, consider whether a Tailwind utility or a main.css component class would be a better fit.

File Map

app/
  assets/
    css/
      main.css          # Global styles, Tailwind layers, component classes
  components/
    ui/
      UiButton.vue      # Variant/size via computed classes
      UiInput.vue       # Error/disabled states via conditional classes
      UiModal.vue       # Branded header, transition animations

Quick Reference

NeedApproach
One-off spacing, color, layoutInline Tailwind utilities in the template
Repeated button/input/card patternUse the Ui* component or a @layer components class
Brand color on a new elementbg-[#117ea7] or text-[#117ea7] (arbitrary value)
Responsive breakpointsm: for small, lg: for desktop layout shifts
Hover/focus/disabled statesTailwind state variants (hover:, focus:, disabled:)
Animations/transitionstransition-colors, transition-all, or Tailwind's built-in animation utilities

CPR - Clinical Patient Records