DOCS LLMs

App Icons and Launch Screens

App icons and launch screens are native binary assets. They belong in railsfast-ios and railsfast-android, not in RailsFast Base.

The web welcome screen is different: Rails owns that screen at app/views/native/auth/show.html.erb. You should make the three surfaces feel continuous, but do not confuse their ownership:

  • the app icon is installed with the native binary
  • the system launch screen is installed with the native binary
  • the first interactive welcome/auth screen is Rails-rendered HTML

A polished launch feels like one continuous surface even though the operating system, native shell, and Rails page render it in three separate phases.

This guide gives you a reproducible workflow for both native repos, including the platform differences that are easy to miss.

Prepare The Canonical Artwork

Start from the same brand source files for both platforms:

  1. App mark -- the compact symbol that still reads at small sizes. Prefer SVG as the canonical source. A large transparent PNG also works.
  2. Wordmark -- the horizontal product logo, exported with transparency. This is useful for the iOS launch surface and the Rails welcome screen.
  3. Brand background color -- one exact sRGB hex value, such as #F3FF6B.
  4. Optional monochrome mark -- a single-color version for Android themed icons and, if you choose, custom iOS tinted icon artwork.

Keep the mark and wordmark separate. A horizontal wordmark usually becomes illegible inside a small launcher icon, while a compact mark often looks too weak on a full-screen launch surface.

IMPORTANT

Do not bake rounded corners, circles, squircle masks, drop shadows, or device chrome into the source artwork. iOS and Android apply their own masks and presentation effects. A pre-masked source produces double corners, excess padding, or clipping on some launchers.

Before exporting:

  • convert text to outlines so missing fonts cannot change the logo
  • trim accidental transparent padding around the source art
  • use sRGB
  • keep edges crisp and high contrast
  • inspect the mark at actual Home Screen size, not only at 1024px
  • commit the generated binary assets so CI and other developers build the same application

The commands below use ImageMagick for deterministic PNG generation:

brew install imagemagick

ImageMagick is optional. Xcode and Android Studio can generate the same outputs through their asset editors. The important part is the output contract, not the specific graphics tool.

iOS App Icon

RailsFast iOS uses a single-size app-icon asset catalog:

RailsFast/Resources/Assets.xcassets/
  AppIcon.appiconset/
    Contents.json
    AppIcon-1024.png

Apple lets iOS and iPadOS generate the installed icon sizes from one 1024x1024 source image. The source must be a square, fully opaque image. Do not add rounded corners; iOS applies the final mask.

Generate The 1024px Source

This example places a transparent mark on an opaque brand-color canvas. The 650x650 mark box is a visual starting point, not an Apple requirement. Adjust it until the symbol has the right optical size while retaining enough margin for the system mask.

# Run from the railsfast-ios repo root.
BRAND_COLOR="#F3FF6B"
MARK_SOURCE="/absolute/path/to/app-mark.png"
ICON_OUTPUT="RailsFast/Resources/Assets.xcassets/AppIcon.appiconset/AppIcon-1024.png"

mkdir -p /tmp/railsfast-brand

magick "$MARK_SOURCE" \
  -trim +repage \
  -resize "650x650>" \
  -gravity center \
  -background none \
  -extent 1024x1024 \
  /tmp/railsfast-brand/icon-mark.png

magick \
  -size 1024x1024 "xc:${BRAND_COLOR}" \
  /tmp/railsfast-brand/icon-mark.png \
  -gravity center \
  -compose over \
  -composite \
  -alpha off \
  -colorspace sRGB \
  "PNG24:${ICON_OUTPUT}"

If the mark starts as SVG, ImageMagick rasterizes it at the requested output size. Inspect the result carefully; SVG files with unsupported filters or external fonts may render differently from the design tool. Exporting a large transparent PNG from the design source first is the conservative fallback.

Verify the basic file contract:

sips \
  -g pixelWidth \
  -g pixelHeight \
  -g hasAlpha \
  RailsFast/Resources/Assets.xcassets/AppIcon.appiconset/AppIcon-1024.png

Expected:

pixelWidth: 1024
pixelHeight: 1024
hasAlpha: no

Then reference the file from RailsFast/Resources/Assets.xcassets/AppIcon.appiconset/Contents.json:

{
  "images" : [
    {
      "filename" : "AppIcon-1024.png",
      "idiom" : "universal",
      "platform" : "ios",
      "size" : "1024x1024"
    }
  ],
  "info" : {
    "author" : "xcode",
    "version" : 1
  }
}

The target already selects this set through ASSETCATALOG_COMPILER_APPICON_NAME: AppIcon in project.yml.

Dark And Tinted iOS Icons

iOS can apply automatic dark and tinted treatments to the primary icon. That is the simplest starting point and requires only the Any 1024px asset above.

If the automatic treatment damages contrast or brand recognition, add explicit Dark and Tinted appearances in Xcode's asset inspector. Apple requires custom tinted artwork to be grayscale. Test all appearances on a real Home Screen; the source PNG can look correct while the system-generated dark treatment looks materially different.

Apple's current asset-catalog documentation:

iOS Launch Screen

RailsFast iOS uses two visually matching phases:

  1. UILaunchScreen -- the static, system-owned launch screen shown before app code runs.
  2. StartupLoadingView -- the app-owned continuation shown while Hotwire loads the first real auth, onboarding, or tab destination.

The two phases deliberately use the same named assets:

RailsFast/Resources/Assets.xcassets/
  LaunchBackground.colorset/
  LaunchLogo.imageset/       # optional but recommended for branded apps

The static launch screen cannot execute code, animate a spinner, fetch data, or contain buttons. Keep it to a background color and logo. StartupLoadingView adds the activity indicator after UIKit takes control.

WARNING

Do not put sign-in buttons, localized marketing copy, a progress percentage, or fake interactive controls into launch artwork. The launch screen is a static placeholder, not the first page of the app.

Set The Background Color

Open Assets.xcassets in Xcode, select LaunchBackground, and set its universal sRGB color to the exact brand value. Using the asset editor avoids manually converting hex channels into the decimal values stored by Contents.json.

If your brand has distinct light and dark launch colors, add an appearance variant intentionally. Otherwise keep one universal color so the launch surface does not change unexpectedly with system appearance.

RailsFast's runtime loading view caps the logo at 288 points wide. These raster sizes therefore form a practical 1x/2x/3x set:

Scale Canvas
1x 288x64 px
2x 576x128 px
3x 864x192 px

The 64-point height is a RailsFast convention, not an Apple platform requirement. It gives a horizontal wordmark a stable transparent canvas while preserving its aspect ratio.

# Run from the railsfast-ios repo root.
WORDMARK_SOURCE="/absolute/path/to/wordmark.png"
LAUNCH_SET="RailsFast/Resources/Assets.xcassets/LaunchLogo.imageset"

mkdir -p "$LAUNCH_SET"

magick "$WORDMARK_SOURCE" \
  -trim +repage \
  -resize 288x64 \
  -gravity center \
  -background none \
  -extent 288x64 \
  "$LAUNCH_SET/LaunchLogo.png"

magick "$WORDMARK_SOURCE" \
  -trim +repage \
  -resize 576x128 \
  -gravity center \
  -background none \
  -extent 576x128 \
  "$LAUNCH_SET/[email protected]"

magick "$WORDMARK_SOURCE" \
  -trim +repage \
  -resize 864x192 \
  -gravity center \
  -background none \
  -extent 864x192 \
  "$LAUNCH_SET/[email protected]"

ImageMagick preserves the wordmark's aspect ratio by default. -extent adds transparent padding to reach the exact canvas; it does not stretch the logo.

Create LaunchLogo.imageset/Contents.json:

{
  "images" : [
    {
      "filename" : "LaunchLogo.png",
      "idiom" : "universal",
      "scale" : "1x"
    },
    {
      "filename" : "[email protected]",
      "idiom" : "universal",
      "scale" : "2x"
    },
    {
      "filename" : "[email protected]",
      "idiom" : "universal",
      "scale" : "3x"
    }
  ],
  "info" : {
    "author" : "xcode",
    "version" : 1
  }
}

Connect The Launch Assets

Configure the static launch screen in project.yml:

# project.yml
targets:
  RailsFast:
    info:
      properties:
        UILaunchScreen:
          UIColorName: LaunchBackground
          UIImageName: LaunchLogo
          UIImageRespectsSafeAreaInsets: true

Then regenerate the Xcode project:

xcodegen generate

Keep the asset names LaunchBackground and LaunchLogo unless you also update StartupLoadingView.backgroundAssetName and StartupLoadingView.logoAssetName. A name mismatch can make the static launch screen or runtime continuation silently lose its artwork.

StartupLoadingView is window-level on purpose. It stays visible across the intentionally blank signed-in /native/entry handoff and disappears only when the final auth, onboarding, or tab destination renders. Request failures remove it immediately so error and retry UI can never remain hidden behind a splash.

Apple's launch-screen references:

Clear The iOS Launch Cache

iOS caches launch-screen snapshots. An ordinary rebuild can continue showing old artwork even when the asset catalog is correct.

For a reliable verification:

  1. Stop the app.
  2. Delete it from the Simulator or device.
  3. Clean/build again.
  4. Reinstall and cold-launch.

From the command line:

BUNDLE_ID="com.example.myapp"

xcrun simctl terminate booted "$BUNDLE_ID" 2>/dev/null || true
xcrun simctl uninstall booted "$BUNDLE_ID" 2>/dev/null || true

Then install/run from Xcode. Apple specifically recommends removing and reinstalling the app when debugging stale launch artwork.

Android Launcher Icon

Android launcher icons are adaptive assets, not one pre-masked square image. The color icon has two independent layers:

  • foreground -- the transparent app mark
  • background -- a full-bleed color or image

Add a monochrome foreground layer so Android can render themed icons. Android launchers apply their own circle, squircle, rounded-square, or OEM-specific mask, so the foreground must survive all of them.

Android's adaptive-icon design contract is:

  • every layer represents a 108x108dp canvas
  • the center 66x66dp is the guaranteed safe zone
  • the outer 18dp on each edge is reserved for masking and motion
  • the visible logo should be at least 48x48dp and no larger than 66x66dp
  • foreground and background layers must not contain their own outer mask or shadow

Source: https://developer.android.com/develop/ui/compose/system/icon_design_adaptive

Generate With Android Studio

Android Studio's Image Asset Studio is the recommended generator because it previews the adaptive masks and creates both modern and legacy resources.

  1. Open the Android repo in Android Studio.
  2. In the Project window, right-click app.
  3. Choose New > Image Asset.
  4. Choose Launcher Icons (Adaptive and Legacy).
  5. Set the asset name to ic_launcher.
  6. On Foreground Layer, select the SVG or large transparent PNG app mark.
  7. Adjust scaling until the meaningful mark stays inside the safe-zone preview.
  8. On Background Layer, choose the exact brand color or a full-bleed image.
  9. On Monochrome Layer, provide the single-color mark when the tool exposes that option.
  10. Inspect circle, squircle, rounded-square, and legacy previews.
  11. Finish and review the generated Git diff before committing it.

Image Asset Studio documentation: https://developer.android.com/studio/write/image-asset-studio

Expected generated resources include:

app/src/main/res/
  mipmap-anydpi-v26/
    ic_launcher.xml
    ic_launcher_round.xml
  mipmap-mdpi/
    ic_launcher.png
    ic_launcher_round.png
  mipmap-hdpi/
    ic_launcher.png
    ic_launcher_round.png
  mipmap-xhdpi/
    ic_launcher.png
    ic_launcher_round.png
  mipmap-xxhdpi/
    ic_launcher.png
    ic_launcher_round.png
  mipmap-xxxhdpi/
    ic_launcher.png
    ic_launcher_round.png

The legacy launcher sizes are 48, 72, 96, 144, and 192 pixels respectively. Let Image Asset Studio generate them; do not hand-resize the already-masked iOS icon into each directory.

RailsFast Android already points the manifest at these names:

<application
    android:icon="@mipmap/ic_launcher"
    android:roundIcon="@mipmap/ic_launcher_round">

If you keep the ic_launcher names, no manifest change is required.

Understand The Template's Layers

The stock template implements the same contract with:

app/src/main/res/
  drawable-nodpi/railsfast_icon.png
  drawable/ic_launcher_background.xml
  drawable/ic_launcher_foreground.xml
  mipmap-anydpi/ic_launcher.xml
  mipmap-anydpi/ic_launcher_round.xml
  mipmap-*/ic_launcher.png
  mipmap-*/ic_launcher_round.png

ic_launcher_foreground.xml applies an 18dp inset before centering the bitmap, which matches Android's reserved outer adaptive-icon region. A downstream app can replace these template resources directly, or let Image Asset Studio generate a conventional mipmap-anydpi-v26 set.

Do not leave stale adaptive XML pointing at the old RailsFast layers. After generation, inspect both mipmap-anydpi and mipmap-anydpi-v26; make sure the resource Android selects references your new foreground, background, and monochrome assets.

An adaptive icon definition should have all three layers:

<?xml version="1.0" encoding="utf-8"?>
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
    <background android:drawable="@drawable/ic_launcher_background" />
    <foreground android:drawable="@drawable/ic_launcher_foreground" />
    <monochrome android:drawable="@drawable/ic_launcher_foreground" />
</adaptive-icon>

Use a dedicated monochrome drawable when the normal foreground contains multiple colors or effects.

Android Splash Screen

RailsFast Android uses the AndroidX SplashScreen API. Do not add a dedicated SplashActivity; Android 12 and newer always create a system starting window, and a second custom splash activity causes duplicated launch screens.

The load-bearing pieces already exist:

  • AuthActivity is the launcher activity.
  • Its manifest theme is Theme.RailsFastAndroid.Splash.
  • AuthActivity.onCreate() calls installSplashScreen() before super.onCreate().
  • The splash theme switches to Theme.RailsFastAndroid through postSplashScreenTheme.

Customize both app/src/main/res/values/themes.xml and app/src/main/res/values-night/themes.xml:

<style name="Theme.RailsFastAndroid.Splash" parent="Theme.SplashScreen">
    <item name="windowSplashScreenAnimatedIcon">@mipmap/ic_launcher</item>
    <item name="windowSplashScreenBackground">@color/app_brand_background</item>
    <item name="postSplashScreenTheme">@style/Theme.RailsFastAndroid</item>
</style>

Add the exact color to app/src/main/res/values/colors.xml:

<color name="app_brand_background">#FFF3FF6B</color>

The first two hex digits are the alpha channel (FF means fully opaque), followed by the six-digit RGB color.

IMPORTANT

Update the night theme deliberately. If the launch surface should always use the brand color, reference the same background in both files. If dark mode intentionally uses a different background, verify the splash icon has enough contrast in both modes. Leaving the template's old night color in place produces a launch screen that changes brand unexpectedly after sunset.

Use The Compact Mark On Android

The Android system splash is not a free-layout screen. Its centered icon is masked using adaptive-icon geometry, and one-third of the foreground can be clipped. The default RailsFast setup therefore uses @mipmap/ic_launcher.

In most products:

  • use the compact app mark on Android's system splash
  • use the horizontal wordmark on iOS when it fits the brand
  • share the exact background color and overall visual language

This intentional asymmetry is more native than forcing the same horizontal asset into both systems.

Android supports windowSplashScreenBrandingImage, but the official design guidance recommends against using it. If you nevertheless have a product requirement for that bottom branding slot, the image must be 200x80dp and must be tested across screen sizes. Do not use it merely to make Android imitate iOS.

The centered splash-icon dimensions are also constrained:

  • with an icon background: 240x240dp canvas, artwork inside a 160dp circle
  • without an icon background: 288x288dp canvas, artwork inside a 192dp circle

Source: https://developer.android.com/develop/ui/views/launch/splash-screen

CAUTION

AndroidX documents a compatibility difference below API 31: an adaptive icon supplied directly as windowSplashScreenAnimatedIcon can be cropped and scaled differently. RailsFast's minimum is API 28, so test at least one API 28-30 emulator. If the result is wrong, use the adaptive foreground drawable as windowSplashScreenAnimatedIcon and set windowSplashScreenIconBackgroundColor to the adaptive background color, as described in the AndroidX SplashScreen API reference: https://developer.android.com/reference/androidx/core/splashscreen/SplashScreen

Do Not Hold The System Splash On Network Work

The system splash disappears when the app draws its first frame. Do not keep it on-screen while waiting for Rails, authentication, or arbitrary network I/O. That turns a system launch affordance into an indefinite blocking screen.

If a visible WebView-loading gap needs product polish, implement a normal, accessible in-app loading surface and remove it on success or failure. Keep the system SplashScreen API responsible only for process/activity startup.

Clear Android Launcher And Splash Caches

Launchers can cache installed icons. Rebuilding the APK is not always enough to prove that a new adaptive icon or splash resource is installed.

Use a clean reinstall:

PACKAGE_ID="com.example.myapp"

adb uninstall "$PACKAGE_ID" 2>/dev/null || true
./gradlew installDebug
adb shell am force-stop "$PACKAGE_ID"
adb shell monkey \
  -p "$PACKAGE_ID" \
  -c android.intent.category.LAUNCHER \
  1

Capture the current emulator screen when reviewing masks or launch behavior:

adb exec-out screencap -p > /tmp/android-launch.png

If the launcher still displays stale art after uninstall/reinstall, restart the emulator or clear the launcher app's cache. Clearing launcher data also resets the Home Screen layout, so use it only on a disposable emulator.

Cross-Platform Verification Checklist

Do not approve icons from source files alone. Verify the installed result.

iOS

  • 1024x1024 source is fully opaque and sRGB.
  • Mark remains legible after the Home Screen mask.
  • Light, Dark, and Tinted Home Screen appearances are acceptable.
  • iPhone and iPad render the expected icon.
  • Cold launch shows the new static background and logo.
  • Static launch screen and StartupLoadingView do not jump in position or size.
  • No white frame appears between native launch and the first Hotwire page.
  • A failed initial request reveals retry/error UI instead of leaving the launch cover forever.
  • Verify at least iOS 18 and iOS 26 when supporting both visual systems.

Android

  • Circle, squircle, rounded-square, and legacy icon previews are acceptable.
  • Foreground mark stays in the adaptive safe zone.
  • Themed icon uses a valid monochrome layer.
  • Light and dark launch themes use intentional colors.
  • Cold start shows one system splash, not a custom-splash duplicate.
  • Android 12+ masking does not clip the splash mark.
  • Test at least one current API emulator and one older supported API level.
  • Verify the installed release build too; resource shrinking and release signing should not change the selected launcher resources.

Both

  • Icon and splash use the same canonical mark/color sources.
  • Native launch styling flows naturally into the Rails welcome/auth page.
  • No buttons or interactive-looking controls appear in static artwork.
  • Brand-specific assets remain downstream app code; only the reusable loading and generation machinery belongs upstream in RailsFast templates.