Skip to content

Kiosk Mode - Purpose-Built Interfaces for Focused Work

Introduction

Kiosk Mode transforms Custom Apps into dedicated, full-screen interfaces optimized for specific tasks and users. Instead of navigating through the full CoCore platform, workers interact with streamlined, purpose-built applications that guide them through exactly what they need to do - nothing more, nothing less.

Think of Kiosk Mode as your ability to create "micro products" within CoCore. Each kiosk is a complete, focused application that feels like custom software built specifically for one task, but with all the power of your production platform behind it.

The Power of Focused Interfaces

Why Traditional Software Fails on the Shop Floor

Picture this: A press operator needs to log job completion. In a traditional system, they must:

  1. Log into a complex system with dozens of menu options
  2. Navigate through multiple screens to find the right job
  3. Understand terminology designed for office workers
  4. Fill out forms with fields they don't understand
  5. Wonder if they did it right
  6. Worry about accidentally changing something important

The result? Workers avoid using the system, data collection is inconsistent, and you're flying blind on production status.

The Kiosk Mode Difference

Now imagine the same scenario with a Kiosk Mode app:

  1. Worker approaches a tablet mounted at their station
  2. Sees only their assigned jobs in large, readable cards
  3. Taps "Start" when beginning, "Complete" when done
  4. Optionally adds a photo or note if there's an issue
  5. Gets immediate confirmation
  6. Returns to work

The entire interaction takes 10 seconds. The data flows instantly into your production system. Everyone wins.

Real-World Use Cases

1. Shop Floor Job Tracking

The Challenge: Production managers need real-time visibility into job progress, but workers find the main system too complex for quick updates.

The Solution: A touch-optimized kiosk app that shows each workstation's current jobs with giant buttons for status updates.

javascript
// Shop Floor Kiosk - Simple Status Updates
const ShopFloorKiosk = {
  template: `
    <div class="kiosk-container">
      <header class="kiosk-header">
        <h1>{{ stationName }}</h1>
        <span class="clock">{{ currentTime }}</span>
      </header>

      <div class="job-grid">
        <div v-for="job in activeJobs"
             :key="job.id"
             class="job-card"
             :class="job.status">

          <div class="job-number">{{ job.number }}</div>
          <div class="job-name">{{ job.name }}</div>
          <div class="customer">{{ job.customerName }}</div>

          <div class="job-actions">
            <button v-if="job.status === 'ready'"
                    @click="startJob(job)"
                    class="btn-start">
              START JOB
            </button>

            <button v-if="job.status === 'in_progress'"
                    @click="completeJob(job)"
                    class="btn-complete">
              MARK COMPLETE
            </button>

            <button @click="reportIssue(job)"
                    class="btn-issue">
              REPORT ISSUE
            </button>
          </div>

          <div class="job-meta">
            Due: {{ formatDate(job.dueDate) }}
            Quantity: {{ job.quantity }}
          </div>
        </div>
      </div>

      <div v-if="showConfirmation" class="confirmation-modal">
        <div class="confirmation-content">
          <div class="success-icon">✓</div>
          <h2>{{ confirmationMessage }}</h2>
        </div>
      </div>
    </div>
  `,

  mounted() {
    // Auto-refresh every 30 seconds
    this.refreshInterval = setInterval(this.loadJobs, 30000);

    // Prevent accidental navigation
    document.addEventListener('contextmenu', e => e.preventDefault());

    // Keep screen awake
    if ('wakeLock' in navigator) {
      navigator.wakeLock.request('screen');
    }
  },

  methods: {
    async startJob(job) {
      // Simple GraphQL mutation to update job status
      await this.updateJobStatus(job.id, 'IN_PROGRESS');
      this.showSuccess('Job Started!');
      this.loadJobs();
    },

    async completeJob(job) {
      // Optional: Show quality checklist first
      if (this.requiresQualityCheck(job)) {
        this.showQualityChecklist(job);
      } else {
        await this.updateJobStatus(job.id, 'COMPLETE');
        this.showSuccess('Job Completed!');
        this.loadJobs();
      }
    }
  }
};

Key Features:

  • Touch-optimized with large buttons
  • Auto-refreshes to show new jobs
  • Prevents accidental navigation
  • Keeps screen awake during shift
  • Visual confirmation of actions
  • Minimal training required

2. Quality Control Checkpoints

The Challenge: Quality control requires consistent checks at specific production stages, but paper checklists get lost and data entry is delayed.

The Solution: Digital quality checkpoints that enforce standards and capture issues in real-time.

javascript
// Quality Control Kiosk
const QualityCheckpoint = {
  template: `
    <div class="qc-kiosk">
      <div class="qc-header">
        <h1>Quality Checkpoint: {{ checkpointName }}</h1>
        <div class="job-info">
          Job: {{ currentJob.number }} | {{ currentJob.name }}
        </div>
      </div>

      <div class="checklist-container">
        <div v-for="item in checklistItems"
             :key="item.id"
             class="checklist-item">

          <div class="item-description">
            {{ item.description }}
            <span v-if="item.specification" class="spec">
              Spec: {{ item.specification }}
            </span>
          </div>

          <div class="item-actions">
            <button @click="markPass(item)"
                    :class="{ active: item.status === 'pass' }"
                    class="btn-pass">
              ✓ PASS
            </button>

            <button @click="markFail(item)"
                    :class="{ active: item.status === 'fail' }"
                    class="btn-fail">
              ✗ FAIL
            </button>
          </div>

          <div v-if="item.status === 'fail'" class="failure-details">
            <textarea v-model="item.failureNotes"
                      placeholder="Describe the issue..."
                      class="failure-notes">
            </textarea>

            <button @click="capturePhoto(item)" class="btn-photo">
              📷 Add Photo
            </button>

            <div v-if="item.photos" class="photo-thumbnails">
              <img v-for="photo in item.photos"
                   :src="photo.thumbnail"
                   @click="viewPhoto(photo)">
            </div>
          </div>
        </div>
      </div>

      <div class="qc-footer">
        <button @click="saveAndContinue"
                :disabled="!allItemsChecked"
                class="btn-continue">
          {{ allPassed ? 'APPROVE & CONTINUE' : 'SUBMIT FOR REVIEW' }}
        </button>

        <div class="inspector-signature">
          Inspector: {{ currentUser.name }}
          Time: {{ currentTime }}
        </div>
      </div>
    </div>
  `,

  methods: {
    async capturePhoto(item) {
      // Use device camera API
      if ('mediaDevices' in navigator) {
        const stream = await navigator.mediaDevices.getUserMedia({
          video: { facingMode: 'environment' }
        });
        // Capture and attach photo to quality record
      }
    },

    async saveAndContinue() {
      const qualityRecord = {
        jobId: this.currentJob.id,
        checkpointId: this.checkpointId,
        inspectorId: this.currentUser.id,
        timestamp: new Date(),
        items: this.checklistItems,
        passed: this.allPassed
      };

      await this.saveQualityRecord(qualityRecord);

      if (!this.allPassed) {
        await this.createQualityAlert(qualityRecord);
      }

      this.showNextJob();
    }
  }
};

Key Features:

  • Enforces quality standards
  • Photo documentation of issues
  • Automatic alerts for failures
  • Digital audit trail
  • Works on tablets with cameras
  • Offline capability with sync

3. Customer Check-In Terminal

The Challenge: Customers arriving to pick up orders need quick service, but front desk staff juggle multiple systems.

The Solution: A self-service or assisted check-in kiosk that streamlines order pickup.

javascript
// Customer Check-In Kiosk
const CustomerCheckIn = {
  template: `
    <div class="checkin-kiosk">
      <div class="welcome-screen" v-if="mode === 'welcome'">
        <div class="logo-container">
          <img src="/company-logo.svg" class="company-logo">
        </div>

        <h1 class="welcome-message">
          Welcome! Here to pick up an order?
        </h1>

        <div class="checkin-options">
          <button @click="startOrderLookup" class="btn-primary">
            📦 Pick Up Order
          </button>

          <button @click="startQuickQuote" class="btn-secondary">
            💰 Get a Quote
          </button>

          <button @click="callAssistance" class="btn-help">
            🔔 Request Help
          </button>
        </div>
      </div>

      <div class="lookup-screen" v-if="mode === 'lookup'">
        <h2>Enter Your Order Information</h2>

        <div class="lookup-methods">
          <div class="lookup-option">
            <label>Order Number</label>
            <input v-model="orderNumber"
                   @keyup.enter="findOrder"
                   placeholder="e.g., ORD-12345"
                   class="order-input">
          </div>

          <div class="divider">OR</div>

          <div class="lookup-option">
            <label>Phone Number</label>
            <input v-model="phoneNumber"
                   @keyup.enter="findByPhone"
                   type="tel"
                   placeholder="(555) 123-4567"
                   class="phone-input">
          </div>
        </div>

        <button @click="findOrder"
                :disabled="!canSearch"
                class="btn-search">
          Search for Order
        </button>
      </div>

      <div class="order-found" v-if="mode === 'found'">
        <div class="order-summary">
          <h2>Order Found!</h2>

          <div class="order-details">
            <div class="detail-row">
              <span>Order:</span>
              <strong>{{ foundOrder.number }}</strong>
            </div>
            <div class="detail-row">
              <span>Customer:</span>
              <strong>{{ foundOrder.customerName }}</strong>
            </div>
            <div class="detail-row">
              <span>Items:</span>
              <strong>{{ foundOrder.itemCount }} items</strong>
            </div>
            <div class="detail-row">
              <span>Status:</span>
              <strong class="status-badge" :class="foundOrder.status">
                {{ foundOrder.statusLabel }}
              </strong>
            </div>
          </div>

          <div v-if="foundOrder.ready" class="ready-message">
            ✅ Your order is ready for pickup!
            <div class="location-info">
              Please proceed to {{ foundOrder.pickupLocation }}
            </div>
          </div>

          <div v-else class="not-ready-message">
            ⏰ Your order is still being prepared
            <div class="eta-info">
              Estimated ready time: {{ foundOrder.estimatedTime }}
            </div>
          </div>

          <button @click="notifyStaff" class="btn-notify">
            Notify Staff I'm Here
          </button>
        </div>
      </div>
    </div>
  `,

  methods: {
    async notifyStaff() {
      // Send notification to fulfillment team
      await this.createNotification({
        type: 'CUSTOMER_ARRIVED',
        orderId: this.foundOrder.id,
        message: `Customer here for ${this.foundOrder.number}`,
        priority: 'high'
      });

      this.showMessage('Staff has been notified!');
    }
  }
};

Key Features:

  • Self-service or staff-assisted modes
  • Multiple lookup methods
  • Real-time order status
  • Automatic staff notifications
  • Queue management integration
  • Branded experience

4. Material Consumption Tracker

The Challenge: Tracking material usage per job is critical for costing but tedious to record manually.

The Solution: Quick-scan kiosks at material storage areas for instant consumption tracking.

javascript
// Material Tracking Kiosk
const MaterialTracker = {
  template: `
    <div class="material-kiosk">
      <div class="scanner-view">
        <div class="scan-instruction">
          Scan job barcode to begin
        </div>

        <input ref="barcodeInput"
               v-model="scannedCode"
               @input="onBarcodeScan"
               class="hidden-input">

        <div v-if="currentJob" class="job-loaded">
          <h2>Job: {{ currentJob.number }}</h2>
          <h3>{{ currentJob.name }}</h3>

          <div class="material-list">
            <h4>Select Material Used:</h4>

            <div class="material-grid">
              <button v-for="material in commonMaterials"
                      @click="selectMaterial(material)"
                      class="material-option">
                <div class="material-icon">{{ material.icon }}</div>
                <div class="material-name">{{ material.name }}</div>
                <div class="material-size">{{ material.size }}</div>
              </button>
            </div>

            <button @click="showAllMaterials" class="btn-more">
              Show All Materials...
            </button>
          </div>

          <div v-if="selectedMaterial" class="quantity-input">
            <h4>Enter Quantity Used:</h4>

            <div class="quick-quantities">
              <button v-for="qty in quickQuantities"
                      @click="setQuantity(qty)"
                      class="qty-button">
                {{ qty }}
              </button>
            </div>

            <input v-model.number="quantity"
                   type="number"
                   class="qty-manual">

            <span class="unit">{{ selectedMaterial.unit }}</span>

            <button @click="recordUsage"
                    :disabled="!quantity"
                    class="btn-record">
              Record Usage
            </button>
          </div>

          <div class="recent-entries">
            <h4>Recent Entries:</h4>
            <div v-for="entry in recentEntries" class="entry">
              {{ entry.material }} - {{ entry.quantity }} {{ entry.unit }}
              <button @click="undoEntry(entry)" class="btn-undo">↶</button>
            </div>
          </div>
        </div>
      </div>
    </div>
  `,

  mounted() {
    // Always focus on barcode input
    this.$refs.barcodeInput.focus();

    // Refocus after any interaction
    document.addEventListener('click', () => {
      this.$refs.barcodeInput.focus();
    });
  },

  methods: {
    async recordUsage() {
      await this.saveMaterialUsage({
        jobId: this.currentJob.id,
        materialId: this.selectedMaterial.id,
        quantity: this.quantity,
        timestamp: new Date(),
        locationId: this.kioskLocation
      });

      // Update inventory
      await this.decrementInventory(
        this.selectedMaterial.id,
        this.quantity
      );

      this.showQuickConfirmation();
      this.resetForNextEntry();
    }
  }
};

Key Features:

  • Barcode scanning integration
  • Quick-select common materials
  • Preset quantity buttons
  • Automatic inventory updates
  • Undo recent entries
  • Job cost tracking

5. Time Clock with Job Assignment

The Challenge: Workers need to clock in/out while managers need to know who's working on what.

The Solution: A unified time and job tracking kiosk that handles both in one interaction.

javascript
// Time & Assignment Kiosk
const TimeClockKiosk = {
  template: `
    <div class="timeclock-kiosk">
      <div class="clock-display">
        {{ currentTime }}
      </div>

      <div class="pin-entry" v-if="!currentWorker">
        <h2>Enter Your PIN</h2>
        <div class="pin-pad">
          <button v-for="n in 9"
                  @click="addDigit(n)"
                  class="pin-button">
            {{ n }}
          </button>
          <button @click="clearPin" class="pin-button">C</button>
          <button @click="addDigit(0)" class="pin-button">0</button>
          <button @click="submitPin" class="pin-button">→</button>
        </div>
        <div class="pin-display">
          {{ '*'.repeat(pin.length) }}
        </div>
      </div>

      <div class="worker-dashboard" v-if="currentWorker">
        <div class="worker-info">
          <img :src="currentWorker.photo" class="worker-photo">
          <div>
            <h2>{{ currentWorker.name }}</h2>
            <div>{{ currentWorker.role }}</div>
          </div>
        </div>

        <div class="clock-actions">
          <button v-if="!currentWorker.clockedIn"
                  @click="clockIn"
                  class="btn-clock-in">
            CLOCK IN
          </button>

          <button v-else
                  @click="clockOut"
                  class="btn-clock-out">
            CLOCK OUT
          </button>

          <button @click="startBreak"
                  v-if="currentWorker.clockedIn && !currentWorker.onBreak"
                  class="btn-break">
            START BREAK
          </button>

          <button @click="endBreak"
                  v-if="currentWorker.onBreak"
                  class="btn-break-end">
            END BREAK
          </button>
        </div>

        <div v-if="currentWorker.clockedIn" class="job-assignment">
          <h3>What are you working on?</h3>

          <div class="assigned-jobs">
            <button v-for="job in assignedJobs"
                    @click="selectJob(job)"
                    :class="{ active: job.id === activeJobId }"
                    class="job-option">
              {{ job.number }} - {{ job.operation }}
            </button>
          </div>

          <button @click="showAllJobs" class="btn-other">
            Other Job...
          </button>
        </div>

        <div class="today-summary">
          <h4>Today's Summary</h4>
          <div>Time Worked: {{ todayHours }}</div>
          <div>Jobs Completed: {{ todayCompletedJobs }}</div>
        </div>
      </div>
    </div>
  `,

  methods: {
    async clockIn() {
      const entry = await this.createTimeEntry({
        workerId: this.currentWorker.id,
        type: 'CLOCK_IN',
        timestamp: new Date()
      });

      // Load assigned jobs for this worker
      await this.loadAssignedJobs();

      this.showMessage(`Clocked in at ${this.formatTime(new Date())}`);
    },

    async selectJob(job) {
      // Track what job worker is on
      await this.updateWorkerActivity({
        workerId: this.currentWorker.id,
        jobId: job.id,
        operationId: job.operationId,
        startTime: new Date()
      });

      this.activeJobId = job.id;
    }
  }
};

Implementation Best Practices

1. Design for the Environment

Physical Considerations:

  • Dirty hands? Use large buttons with high contrast
  • Noisy environment? Use visual feedback not just audio
  • Gloves worn? Increase touch targets to 60px minimum
  • Standing users? Position key actions in comfortable reach zones

Environmental Hardening:

css
/* Kiosk-optimized styles */
.kiosk-container {
  /* Prevent text selection */
  user-select: none;
  -webkit-user-select: none;

  /* Disable pull-to-refresh */
  overscroll-behavior-y: contain;

  /* High contrast for bright environments */
  background: #000;
  color: #FFF;

  /* Large, readable text */
  font-size: 20px;
  font-weight: 500;
}

.kiosk-button {
  /* Minimum touch target */
  min-height: 60px;
  min-width: 120px;

  /* Clear touch feedback */
  transition: transform 0.1s;
}

.kiosk-button:active {
  transform: scale(0.95);
  background: #00FF00;
}

2. Handle Failure Gracefully

Kiosks often run in challenging network environments:

javascript
// Robust error handling
const ResilientKiosk = {
  data() {
    return {
      offline: false,
      pendingActions: [],
      lastSync: null
    };
  },

  mounted() {
    // Monitor connection
    window.addEventListener('online', this.syncPendingActions);
    window.addEventListener('offline', () => this.offline = true);

    // Heartbeat check
    setInterval(this.checkConnection, 30000);
  },

  methods: {
    async saveAction(action) {
      try {
        if (this.offline) {
          // Queue for later
          this.pendingActions.push(action);
          localStorage.setItem('pending', JSON.stringify(this.pendingActions));
          this.showMessage('Saved locally - will sync when online');
          return;
        }

        await this.sendToServer(action);
        this.showMessage('✓ Saved');

      } catch (error) {
        // Fallback to local storage
        this.pendingActions.push(action);
        this.showMessage('Connection issue - saved locally');
      }
    },

    async syncPendingActions() {
      if (this.pendingActions.length === 0) return;

      for (const action of this.pendingActions) {
        try {
          await this.sendToServer(action);
        } catch (e) {
          console.error('Sync failed for action', e);
          break;
        }
      }

      this.pendingActions = [];
      localStorage.removeItem('pending');
      this.showMessage('✓ All changes synced');
    }
  }
};

3. Optimize for Speed

Every second counts in production environments:

javascript
// Performance optimizations
const FastKiosk = {
  setup() {
    // Preload next likely screens
    const preloadNextJob = () => {
      const nextJob = this.jobQueue[0];
      if (nextJob) {
        this.prefetchJobData(nextJob.id);
      }
    };

    // Debounce updates
    const debouncedSave = debounce(this.saveChanges, 1000);

    // Use optimistic updates
    const optimisticUpdate = (action) => {
      // Update UI immediately
      this.updateLocalState(action);

      // Save in background
      this.saveToServer(action).catch(error => {
        // Rollback on failure
        this.rollbackLocalState(action);
        this.showError('Failed to save');
      });
    };

    return {
      preloadNextJob,
      debouncedSave,
      optimisticUpdate
    };
  }
};

4. Build for Non-Technical Users

Remember your audience:

javascript
// User-friendly error messages
const UserFriendlyKiosk = {
  methods: {
    handleError(error) {
      // Instead of: "GraphQL Error: Failed to execute mutation updateJobStatus"
      // Show: "Could not update job. Please try again or call supervisor."

      const userMessage = this.getUserMessage(error);

      this.showError({
        title: 'Something went wrong',
        message: userMessage,
        actions: [
          { label: 'Try Again', action: this.retry },
          { label: 'Call Supervisor', action: this.alertSupervisor }
        ]
      });
    },

    getUserMessage(error) {
      const errorMap = {
        'NETWORK_ERROR': 'Internet connection problem. Please wait a moment.',
        'AUTH_ERROR': 'Please sign in again.',
        'NOT_FOUND': 'Could not find that item. It may have been moved.',
        'PERMISSION': 'You need supervisor approval for this action.'
      };

      return errorMap[error.code] || 'Please try again or ask for help.';
    }
  }
};

Deployment Strategies

Single-Purpose Devices

Deploy dedicated tablets or terminals for specific functions:

javascript
// Kiosk device configuration
const kioskConfig = {
  // Lock to single app
  kioskMode: true,

  // Prevent navigation
  preventNavigation: true,

  // Auto-launch on boot
  autoStart: true,

  // Screen settings
  screen: {
    brightness: 80,
    timeout: 'never',
    orientation: 'landscape'
  },

  // Network restrictions
  network: {
    allowedDomains: ['*.cococo.com'],
    blockDownloads: true
  }
};

Multi-Station Deployment

Deploy the same kiosk app to multiple locations with location awareness:

javascript
// Location-aware kiosk
const MultiStationKiosk = {
  async mounted() {
    // Identify station from device or network
    this.station = await this.identifyStation();

    // Load station-specific configuration
    this.config = await this.loadStationConfig(this.station.id);

    // Filter jobs for this station
    this.jobs = await this.loadStationJobs(this.station.id);

    // Customize UI for station type
    this.applyStationTheme(this.station.type);
  }
};

Progressive Deployment

Start small and expand:

  1. Pilot Phase: Deploy to one workstation
  2. Department Rollout: Expand to full department
  3. Cross-Functional: Add integration points
  4. Full Production: Deploy across entire operation

Security Considerations

Access Control

javascript
// Role-based kiosk access
const SecureKiosk = {
  async beforeMount() {
    // Require authentication
    if (!this.user) {
      this.requireLogin();
      return;
    }

    // Check permissions
    if (!this.hasPermission('kiosk.production')) {
      this.showError('Not authorized for this kiosk');
      return;
    }

    // Log access
    await this.logAccess({
      userId: this.user.id,
      kioskId: this.kioskId,
      timestamp: new Date()
    });
  }
};

Data Protection

javascript
// Protect sensitive data
const PrivacyKiosk = {
  methods: {
    // Auto-logout after inactivity
    startInactivityTimer() {
      clearTimeout(this.inactivityTimer);
      this.inactivityTimer = setTimeout(() => {
        this.logout();
        this.showLoginScreen();
      }, 5 * 60 * 1000); // 5 minutes
    },

    // Clear data on logout
    logout() {
      this.clearLocalStorage();
      this.clearSessionData();
      this.resetToLoginScreen();
    }
  }
};

Success Metrics

Track the impact of your kiosk deployments:

  1. Efficiency Metrics

    • Time per transaction (before vs. after)
    • Data entry errors reduced
    • Training time decreased
  2. Adoption Metrics

    • Daily active users
    • Transactions per day
    • User satisfaction scores
  3. Business Metrics

    • Real-time data accuracy
    • Decision-making speed
    • Cost savings from automation

Conclusion

Kiosk Mode transforms Custom Apps into powerful, purpose-built tools that meet workers where they are. By removing complexity and focusing on specific tasks, you create interfaces that people actually want to use. The result is better data, happier workers, and more efficient operations.

Remember: The best kiosk is invisible. Workers shouldn't think about the technology - they should just get their job done faster and easier than before. When you achieve that, you've built a successful kiosk application.

Start with one focused use case, prove the value, then expand. Your shop floor, warehouse, and front office will thank you.

Connect. Combine. Collaborate.
The pioneering open integration platform, dedicated to transforming connectivity in the printing industry.