design/raycast
Raycast: Sztuka interfejsu produktywności
„Wierzymy, że najlepsze narzędzia nie przeszkadzają w pracy." — Zespół Raycast
Raycast to launcher dla macOS, który zastąpił Spotlight wśród zaawansowanych użytkowników. Pokazuje, jak projektować pod kątem ekstremalnej wydajności, zachowując jednocześnie ciepło i osobowość.
Dlaczego Raycast ma znaczenie
Raycast udowadnia, że utylitarne oprogramowanie nie musi być zimne. Łączy: - Błyskawiczną szybkość (uruchomienie w <50ms) - Interakcję priorytetowo z klawiatury - Rozszerzalność przez sklep - Osobowość poprzez przyjemne detale
Kluczowe osiągnięcia: - Na nowo zdefiniował, czym może być launcher - Udowodnił, że narzędzia deweloperskie mogą mieć osobowość - Stworzył prosperujący ekosystem rozszerzeń - Ustanowił nowe standardy dla natywnego designu macOS
Kluczowe wnioski
- 50ms to próg szybkości - Użytkownicy zauważają każde opóźnienie powyżej 50ms; traktuj to jako twarde ograniczenie inżynieryjne, nie wytyczną
- Natywna integracja z platformą buduje zaufanie - Vibrancy, SF Symbols, systemowe kolory akcentów i standardowe skróty sprawiają, że Raycast czuje się jak część macOS
- Skróty klawiszowe muszą być odkrywalne - Pokazuj skróty bezpośrednio w wynikach (⌘1, ⌘2), nie ukryte w tooltipach czy dokumentacji
- Osobowość bez kosztu wydajności - Konfetti przy osiągnięciach, zabawne puste stany i sprytne teksty tworzą radość bez dodawania opóźnień
- Ekosystemy rozszerzeń potrzebują języka projektowego - Rozszerzenia zewnętrzne wyglądają natywnie, ponieważ używają tych samych komponentów formularzy, paneli akcji i wzorców nawigacji
Podstawowe zasady projektowania
1. Natychmiastowa odpowiedź
Raycast czuje się jak przedłużenie twoich myśli. Bez opóźnień. Bez czekania.
Jak to osiągają: - Natywna implementacja w Swift (nie Electron) - Wstępnie załadowany indeks rozszerzeń - Agresywne cache'owanie - Zoptymalizowany potok renderowania
Zasada 50ms: Jeśli coś trwa dłużej niż 50ms, użytkownicy to zauważają. Raycast traktuje to jako twarde ograniczenie.
Implikacje projektowe: - Pokazuj UI natychmiast, ładuj dane asynchronicznie - Stany szkieletowe dla ciężkich operacji - Nigdy nie blokuj głównego wątku - Preferuj lokalne przetwarzanie nad wywołania sieciowe
// Wzorzec natychmiastowej odpowiedzi w stylu Raycast
struct SearchView: View {
@State private var results: [Result] = []
@State private var isLoading = false
var body: some View {
VStack {
// Pokaż natychmiast z danymi z cache
ForEach(cachedResults) { result in
ResultRow(result)
}
if isLoading {
// Dyskretny wskaźnik ładowania
ProgressView()
.scaleEffect(0.5)
}
}
.task(id: query) {
isLoading = true
results = await search(query)
isLoading = false
}
}
}
2. Głęboka integracja z macOS
Raycast czuje się jak naturalny element macOS. Respektuje konwencje platformy, jednocześnie przesuwając granice.
Natywne zachowania: - Vibrancy i rozmycie (NSVisualEffectView) - Systemowe kolory akcentów - SF Symbols wszędzie - Natywne skróty klawiszowe - Respektuje systemowy tryb ciemny/jasny
Implementacja:
// Vibrancy macOS
struct RaycastWindow: View {
var body: some View {
VStack {
// Zawartość
}
.background(.ultraThinMaterial) // Natywne rozmycie
.clipShape(RoundedRectangle(cornerRadius: 12, style: .continuous))
}
}
// Systemowy kolor akcentu
Text("Zaznaczone")
.foregroundStyle(.tint) // Podąża za preferencjami systemu
// SF Symbols z kolorami semantycznymi
Image(systemName: "checkmark.circle.fill")
.foregroundStyle(.green)
Projekt okna:
┌────────────────────────────────────────────────────────────┐
│ ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ │
│ ░░ ░░ │
│ ░░ > Szukaj w Raycast... ░░ │
│ ░░ ░░ │
│ ░░ ┌────────────────────────────────────────────────┐ ░░ │
│ ░░ │ [A] Aplikacje Cmd+1 │ ░░ │
│ ░░ │ [F] Wyszukiwanie plików Cmd+2 │ ░░ │
│ ░░ │ [C] Historia schowka Cmd+3 │ ░░ │
│ ░░ │ [S] Polecenia systemowe Cmd+4 │ ░░ │
│ ░░ └────────────────────────────────────────────────┘ ░░ │
│ ░░ ░░ │
│ ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ │
└────────────────────────────────────────────────────────────┘
↑ Vibrancy pokazuje pulpit przez okno
3. Doskonałość klawiatury z wizualnym feedbackiem
Każda akcja ma skrót klawiszowy. Każdy skrót ma wizualne potwierdzenie.
Wzorce wyświetlania skrótów:
LISTA WYNIKÓW:
┌────────────────────────────────────────────────────────────┐
│ Ostatnie aplikacje │
│ │
│ (*) Visual Studio Code Cmd+1 │
│ ~/Projects/my-app │
│ │
│ ( ) Figma Cmd+2 │
│ ~/Design/project.fig │
│ │
│ ( ) Terminal Cmd+3 │
│ │
│ ───────────────────────────────────────────────────────────│
│ Akcje Cmd+K lub -> aby zobaczyć │
└────────────────────────────────────────────────────────────┘
Panel akcji:
┌────────────────────────────────────────────────────────────┐
│ Akcje dla "Visual Studio Code" esc <- │
│ │
│ Otwórz Enter │
│ Otwórz w nowym oknie Cmd+Enter │
│ ──────────────────────────────────────────────────────── │
│ Pokaż w Finderze Cmd+Shift+F │
│ Kopiuj ścieżkę Cmd+Shift+C │
│ ──────────────────────────────────────────────────────── │
│ Przenieś do Kosza Cmd+Backspace │
└────────────────────────────────────────────────────────────┘
Zasady projektowania: - Pokazuj skróty inline (nie w tooltipach) - Grupuj skróty według klawisza modyfikującego - Używaj standardowych skrótów macOS gdzie oczekiwane - Wzorce mnemoniczne (⌘⇧F = Finder)
4. Osobowość w szczegółach
Raycast jest wydajny, ale nie sterylny. Małe przyjemności czynią go niezapomnianym.
Przykłady: - Konfetti przy osiągnięciach - Subtelne dźwięki dla akcji - Zabawne puste stany - Easter eggi dla zaawansowanych użytkowników
Przykład pustego stanu:
┌────────────────────────────────────────────────────────────┐
│ │
│ [?] │
│ │
│ Brak wyników dla "asdfgh" │
│ │
│ Spróbuj wyszukać coś innego, │
│ lub sprawdź Sklep z rozszerzeniami │
│ │
│ [Przeglądaj rozszerzenia] │
│ │
└────────────────────────────────────────────────────────────┘
Wzorzec konfetti (dla osiągnięć):
// Po ukończeniu onboardingu, zdobyciu odznaki itp.
ConfettiView()
.transition(.opacity)
.animation(.spring(), value: showConfetti)
Wytyczne dotyczące osobowości: - Radość nie powinna opóźniać - Świętowania są zasłużone (nie losowe) - Osobowość w tekstach („Brak wyników dla…" vs „Błąd: 0 wyników") - Dźwięk jest opcjonalny i dyskretny
5. Projektowanie ekosystemu rozszerzeń
Sklep z rozszerzeniami Raycast jest pięknie zaprojektowany.
Karta rozszerzenia:
┌────────────────────────────────────────────────────────────┐
│ ┌──────┐ │
│ │ [+] │ GitHub │
│ │ │ Szukaj repozytoriów, PR-ów i issues │
│ └──────┘ │
│ ★★★★★ 1.2k ocen • Od Raycast │
│ │
│ [Zainstaluj] │
└────────────────────────────────────────────────────────────┘
Widok szczegółów rozszerzenia:
┌────────────────────────────────────────────────────────────┐
│ │
│ ← GitHub [Odinstaluj]│
│ │
│ ┌──────────────────────────────────────────────────────┐ │
│ │ │ │
│ │ [Zrzut ekranu rozszerzenia w użyciu] │ │
│ │ │ │
│ └──────────────────────────────────────────────────────┘ │
│ │
│ Polecenia │
│ ├─ Szukaj repozytoriów │
│ ├─ Moje Pull Requesty │
│ ├─ Szukaj Issues │
│ └─ Utwórz Issue │
│ │
│ Historia zmian │
│ ├─ v2.1.0 Dodano filtrowanie organizacji │
│ └─ v2.0.0 Kompletne przepisanie z nowym API │
│ │
└────────────────────────────────────────────────────────────┘
6. Projektowanie formularzy dla rozszerzeń
Rozszerzenia używają spójnego systemu formularzy.
Wzorce formularzy:
┌────────────────────────────────────────────────────────────┐
│ Utwórz GitHub Issue esc │
│ │
│ Repozytorium │
│ ┌──────────────────────────────────────────────────────┐ │
│ │ raycast/extensions ▾ │ │
│ └──────────────────────────────────────────────────────┘ │
│ │
│ Tytuł │
│ ┌──────────────────────────────────────────────────────┐ │
│ │ Bug: Wyszukiwanie nie działa │ │
│ └──────────────────────────────────────────────────────┘ │
│ │
│ Opis │
│ ┌──────────────────────────────────────────────────────┐ │
│ │ Kiedy szukam... │ │
│ │ │ │
│ │ │ │
│ └──────────────────────────────────────────────────────┘ │
│ │
│ Etykiety (opcjonalne) │
│ ┌──────────────────────────────────────────────────────┐ │
│ │ [bug] [needs-triage] + │ │
│ └──────────────────────────────────────────────────────┘ │
│ │
│ [Utwórz Issue ⌘↵] [Anuluj esc] │
│ │
└────────────────────────────────────────────────────────────┘
Stylowanie komponentów formularza:
// Formularz w stylu Raycast w SwiftUI
Form {
Section("Repozytorium") {
Picker("", selection: $repo) {
ForEach(repos) { repo in
Text(repo.name).tag(repo)
}
}
.labelsHidden()
}
Section("Tytuł") {
TextField("Bug: ...", text: $title)
}
Section("Opis") {
TextEditor(text: $description)
.frame(minHeight: 100)
}
}
.formStyle(.grouped)
Wzorce projektowe do nauki
Wzorzec „najpierw wyszukiwanie"
Wszystko zaczyna się od wyszukiwania.
┌────────────────────────────────────────────────────────────┐
│ > | │
│ ^ Kursor tutaj natychmiast po uruchomieniu │
│ │
│ Wpisz, aby szukać poleceń, aplikacji, plików i więcej │
└────────────────────────────────────────────────────────────┘
Implementacja: - Auto-focus na wyszukiwarce przy uruchomieniu - Wyszukiwanie rozmyte z podświetleniem - Inteligentne rankingowanie (ostatnie + częste + trafne) - Wyniki pojawiają się podczas pisania
Nawigacja hierarchiczna
Głęboka funkcjonalność bez złożoności.
Poziom 1: Główne wyszukiwanie
↓ Enter
Poziom 2: Polecenia rozszerzenia
↓ Enter
Poziom 3: Wyniki akcji
↓ ⌘K
Poziom 4: Panel akcji
Zawsze: esc cofa o jeden poziom
Potwierdzenia HUD
Szybki feedback bez przerywania.
Po akcji:
┌────────────────────────┐
│ ✓ Skopiowano do schowka│
└────────────────────────┘
↑ Pojawia się na chwilę, zanika
Nie wymaga zamknięcia
// Wzorzec HUD
struct HUDView: View {
@State private var show = false
var body: some View {
if show {
Text("✓ Skopiowano")
.padding(.horizontal, 16)
.padding(.vertical, 8)
.background(.regularMaterial)
.cornerRadius(8)
.transition(.opacity.combined(with: .scale))
.onAppear {
DispatchQueue.main.asyncAfter(deadline: .now() + 1.5) {
withAnimation { show = false }
}
}
}
}
}
Co warto zapożyczyć od Raycast
Dla interfejsu launchera/wyszukiwarki
- 50ms albo nic - Szybkość jest niepodlegająca dyskusji
- Skróty klawiszowe wszędzie - Odkrywalne, nie ukryte
- Wyszukiwanie rozmyte z podświetleniem - Pokaż dlaczego wyniki pasują
- Panele akcji (⌘K) - Akcje kontekstowe
- Potwierdzenia HUD - Szybki, nieblokujący feedback
Dla aplikacji macOS
- Natywny vibrancy - Używaj NSVisualEffectView/.material
- SF Symbols - Spójne z systemem
- Kolory systemowe - Respektuj preferencje koloru akcentu
- Ciągłe zaokrąglenia - .cornerRadius(style: .continuous)
- Standardowe skróty - Nie wymyślaj na nowo ⌘C, ⌘V, itp.
Konkretne techniki
| Technika | Jak zastosować |
|---|---|
| Natychmiastowe uruchomienie | Wstępnie rozgrzej okno, leniwie ładuj zawartość |
| Wyszukiwanie rozmyte | Użyj Fuse.js lub podobnej biblioteki |
| Podpowiedzi skrótów | Inline ⌘1, ⌘2, itp. w wynikach |
| Panel akcji | Drugorzędne akcje na ⌘K lub → |
| Feedback HUD | W stylu toast, auto-znikanie |
| API rozszerzeń | Jasne kontrakty, spójne UI |
System kolorów
Raycast używa semantycznych kolorów powiązanych z systemem.
// Semantyczne kolory inspirowane Raycast
extension Color {
static let rayBackground = Color(nsColor: .windowBackgroundColor)
static let raySecondary = Color.secondary
static let rayTertiary = Color(nsColor: .tertiaryLabelColor)
// Kolory statusów
static let raySuccess = Color.green
static let rayWarning = Color.orange
static let rayError = Color.red
// Akcent podąża za systemem
static let rayAccent = Color.accentColor
}
// Przykład trybu ciemnego
struct ResultRow: View {
var body: some View {
HStack {
Image(systemName: "app.fill")
.foregroundStyle(.secondary)
Text("Nazwa aplikacji")
.foregroundStyle(.primary)
Spacer()
Text("⌘ 1")
.font(.caption)
.foregroundStyle(.tertiary)
.padding(.horizontal, 6)
.padding(.vertical, 2)
.background(Color.secondary.opacity(0.2))
.cornerRadius(4)
}
.padding(.horizontal, 12)
.padding(.vertical, 8)
}
}
Kluczowe spostrzeżenia
„Każda milisekunda ma znaczenie. Użytkownicy czują różnicę między 50ms a 100ms."
„Skróty klawiszowe powinny być odkrywalne, nie zapamiętywane z dokumentacji."
„Osobowość sprawia, że oprogramowanie jest pamiętane. Wydajność sprawia, że jest niezastąpione."
„Rozszerzenia powinny czuć się natywnie, nie przyklejone. Ten sam język UI, te same interakcje."
Często zadawane pytania
Jak Raycast osiąga czasy odpowiedzi poniżej 50ms?
Raycast używa natywnej implementacji w Swift (nie Electron), wstępnie ładuje indeks rozszerzeń przy uruchomieniu, agresywnie cache'uje wyniki wyszukiwania i optymalizuje potok renderowania. UI pojawia się natychmiast, podczas gdy dane ładują się asynchronicznie. Próg 50ms jest traktowany jako twarde ograniczenie, nie cel.
Dlaczego Raycast wygląda tak natywnie dla macOS?
Raycast używa NSVisualEffectView dla vibrancy/rozmycia, SF Symbols dla wszystkich ikon, systemowych kolorów akcentów respektujących preferencje użytkownika i standardowych skrótów klawiszowych macOS. Okno ma ciągły promień zaokrąglenia i automatycznie podąża za systemowym trybem ciemnym/jasnym.
Jakie jest podejście Raycast do skrótów klawiszowych?
Każda akcja ma skrót klawiszowy wyświetlany inline w wynikach (⌘1, ⌘2, itp.), nie ukryty w tooltipach. Skróty używają wzorców mnemonicznych (⌘⇧F dla Findera, ⌘⇧C dla Kopiuj ścieżkę). Panel akcji (⌘K) zapewnia drugorzędne akcje z własnymi skrótami.
Jak Raycast dodaje osobowość bez spowalniania?
Świętowania są zasłużone i krótkie — konfetti pojawia się po osiągnięciach, nie losowo. Puste stany mają zabawne teksty („Brak wyników dla…" zamiast „Błąd: 0 wyników"). Dźwięki są opcjonalne i dyskretne. Radość nigdy nie blokuje użytkownika ani nie dodaje opóźnień.
Jak system rozszerzeń Raycast utrzymuje spójność?
Wszystkie rozszerzenia używają tych samych komponentów UI (formularze, listy, panele akcji), podążają za tymi samymi wzorcami nawigacji (Enter żeby zagłębić się, Escape żeby wrócić) i dziedziczą stylowanie systemowe. API wymusza spójność, więc rozszerzenia zewnętrzne są nie do odróżnienia od wbudowanych funkcji.
Zasoby
- Strona: raycast.com
- Sklep: raycast.com/store - Przestudiuj design rozszerzeń
- Dokumentacja: Wytyczne tworzenia rozszerzeń
- Blog: Posty inżynieryjne o wydajności
- GitHub: github.com/raycast - Rozszerzenia open source